Pull to refresh

Comments 858

Я нахожу несколько забавным, что раньше в комментах автор высказывал своё «фе» в адрес типов в угоду тестам, но в статье о тестах уже предпочёл умолчать.

И да, типы на пригодных для использования языках — часть документации. Рассуждения о том, что она может устареть, — абсурдны. Код тоже может устареть, вообще-то, почему этого никто не боится?

А кто вам, собственно, сказал, что устаревания кода никто не боится? Вы вообще, например, в курсе, какой объем айтишной экономики ушел на то, чтоб слезть с флеша? Это всё потому, что никто не боится устаревания кода, ага.

Ну и касательно документации — это как с бекапами. Есть люди, которые еще не работали с устаревшей, неактуальной, или попросту плохой документацией, и есть люди, которые уже работали. Удивляться тому, что последние предпочитают при малейших сомнениях глянуть в код — как минимум странно. Код не врёт (особенно если вы его сами собрали), а установить, врёт ли документация — не представляется возможным без существенных расходов.
Я нахожу несколько забавным, что раньше в комментах автор высказывал своё «фе» в адрес типов в угоду тестам, но в статье о тестах уже предпочёл умолчать.

Диффамация, я такого в общем случае никогда не утверждал. А даже если бы и утверждал — почему я должен говорить о тестах в заметке, посвещенной типам? А если и должен — то вот же внезапно оно: «подробную документацию, с соответствующими doctest».


установить, врёт ли документация — не представляется возможным без существенных расходов

А зачем вообще добровольно использовать инструменты, которым вы не доверяете? Документация в тулчейне, которым я пользуюсь, — не врет. Никогда.

А зачем вообще добровольно использовать инструменты, которым вы не доверяете? Документация в тулчейне, которым я пользуюсь, — не врет. Никогда.

Зачем быть бедным и больным, если можно быть богатым и здоровым?

Ну и вы передёргиваете: если у меня есть возможность заглянуть в код — то я вполне могу доверять инструментам (код хороший) и при этом не доверять документации (документация плохая).

Я черным по белому написал: инструмент может быть признан хорошим тогда и только тогда, когда у него идеальная документация.

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

Я ж говорю, это аргумент из серии «ну вот смотрите, я здоровый и богатый, чё б вам всем такими не быть»?

Неправда. Мы обсуждаем код (и типы, и документацию) — написанную нами самими. Никаких проблем создавать документацию, тут не нужно быть миллиардером.

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

Действительно, никаких проблем: вот тебе многомегабайтный легаси-код, который по чисто экономическим причинам мы несколько лет поддерживали силами одного полупрофессионала, а теперь наверни нам на него модную обёртку. И, опять же, по чисто экономическим причинам, времени на то, чтоб написать/обновить документацию легаси у тебя не будет.

Действительно, и при чём тут экономика и миллиардеры?

ЗЫ: Всё вышенаписанное — это, к слову, не абстракция, а личный опыт.
вот тебе многомегабайтный легаси-код

Там и типов нет, наверное ведь. Давайте сравнивать сравнимое, пожалуйста.

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

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

>А зачем вообще добровольно использовать инструменты, которым вы не доверяете?
Хм. Вот у меня инструменты допустим Hadoop и Spark. Ну, там еще с десяток других наверное наберется, но это уже неважно. Вы думаете, у меня есть много альтернатив? Я бы сказал, что нет — ни одной вменяемой не наблюдается. И даже если бы они были, стоимость перехода на любую из них будет совершенно запредельна.
К разделу «Неверная ветка»: вообще для таких случаев есть методы вида valueOf.

Не понимаю, если честно, как valueOf поможет в случае, когда мы перепутали что вернуть (там в примере перепутаны, грубо говоря, if и unless).

UFO just landed and posted this here

Я 20 лет без малого пишу на perl, и мне не хватает типов. Как только мы выходим за пределы sum, начинается веселье. Я сейчас поддерживаю старое процедурное cgi легаси, и мне очень, очень непросто ловить ошибки.

UFO just landed and posted this here
вот чтобы проблема и "а были бы типы, проблема была бы решена"

Я вот, кстатит, легко приведу.


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

Так сделайте это для этой конкретной структуры, в чем проблема-то? Ну там, не знаю, класс с лоадером создайте.

Что "это" вы мне предлагаете сделать для конкретной структуры?

Возвращать «полностью идентичную структуру данных», пропуская сырые данные через валидатор.

пропуская сырые данные через валидатор.

… который надо написать, да? И чем это от тестов отличается?

>Если силы потраченные на сильную типизацию потратить на написание тестов, то результат будет лучше!
Не будет. Тесты вам почти ничего не гарантируют, в отличие от типов.

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

Для тестов же это неверно в общем случае — если тест отработал, это может вообще ничего не значить.

Кроме того, то же самое, что обычно сложно описать на типах — так же сложно описать и в тестах. Условный немного пример — пусть вам нужна устойчивая сортировка. Попробуйте сформулировать тест, который проверит этот факт?

>программист делает АЛГОРИТМИЧЕСКУЮ ошибку и типы никак не помогают
Вы ждете от типов, что они вам исправят использование минуса вместо плюса, или речь о чем-то другом?
UFO just landed and posted this here
>тесты нам гарантируют что код выполняется будучи прогнан по набору тестовых входных вариантов.
Ну вообще-то это и называется «не гарантирует» ))) На этом работает — и больше возможно ни на чем. Или если быть точным — тесты не гарантируют отсутствия ошибок.

По поводу практически применимых языков — ну вот смотрите, у 0xd34df00d был пост с в общем-то простым примером — инвертированием списка. И код, реализованный без единого запуска, но зато с доказательством. Суть которого проста, как огурец — инвертированный список должен в качестве результата левой свертки давать то же значение, какое не инвертированный дает для свертки правой. Или наоборот, не суть.

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

Автор, кстати, предлагает вполне разумные вещи. Guards там, или паттерн матчинг, тотальные проверки аргументов функций. И по сути, строгая типизация, как я ее вижу, это примерно те же самые идеи, только выраженные другими терминами. По сути, guard это и есть то, что хотелось бы уметь выразить в типах функции и ее аргументов — только записанный в явном виде в коде. Ну и примененный в рантайме, а не при компиляции.
UFO just landed and posted this here
задача простого http-сервера — решается рядовым Джуном.

Серьезно?

UFO just landed and posted this here

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

UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here
а проблемы находятся тестами.

… которые, конечно, любой джун умеет писать.

UFO just landed and posted this here

а джуну говоришь "у нас такая парадигма: сперва пишем типы, затем пишем код" и он ей следует.

UFO just landed and posted this here
с написанием на типах простого http эксперты не справились.

А вы можете доказать это утверждение как-то?

UFO just landed and posted this here

Нет там пруфа утверждению "эксперты не справились с написанием на типах простого http". Ни в части "эксперты", ни в части "простого".

UFO just landed and posted this here
проект-флагман
проект-"самый быстрый сервер!"

И именно это опровергает ваше утверждение про "простой http".

UFO just landed and posted this here

Проект "самый быстрый сервер" — это не "простой http". Простой http — это тот, который поддерживает протокол.

UFO just landed and posted this here
самый быстрый — это тот который использует самый быстрый механизм операционной системы работы с сокетами.

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


первично что проект "самый быстрый" == "ничего нового в алгоритмах"

Совершенно не обязательно.

UFO just landed and posted this here
ибо скорость определяет именно он (самое узкое место в нём).

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

UFO just landed and posted this here
над этим вопросом работают много коллективов и в том числе разработчики OS.

Какое отношение "разработчики OS" имеют к тому, где у меня в коде узкое место?


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

UFO just landed and posted this here
и если Вы претендуете на звание "самый быстрый", то это значит на своей стороне сделали всё возможное чтобы скорость была максимальна.

… и вот ровно это и не является простой задачей, о чем и шла речь с самого начала.


всё равно идёт вызов системного метода.
который и будет самым медленным во всех реализациях http

Конечно, не во всех.

UFO just landed and posted this here
потому что ничего алгоритмически нового в ней сделать уже нельзя

Не-новое — не обязательно простое.

UFO just landed and posted this here

… и вы предлагаете вам просто поверить на слово в этом, так ведь?

UFO just landed and posted this here
любой джун реализует http сервер.

Да-да-да, вы уже это говорили. Этому вы тоже предлагаете верить на слово.


потом попрофилирует код

Знаете, в моем понимании джуны особо не умеют код профилировать.

UFO just landed and posted this here
Вы предлагаете верить на слово тому что джуны не умеют профилировать.

Нет, я предлагаю верить на слово тому, что в моем понимании джуны не умеют профилировать.


давайте теперь, докажите это Ваше утверждение

Утверждение о моем понимании чего бы то ни было доказывается очень просто: я — единственный авторитетный источник в этой области.


в общем очень много что студенты изучают до того как они придут ко мне и станут джунами.

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

UFO just landed and posted this here
почему я должен верить Вашему пониманию

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


средний джун учился в ВУЗе

… каком-то, наверное, да. Как-то учился.

джун или учился в ВУЗе или изучал это самостоятельно

средний джун учился в ВУЗе

Хм… Ну да, учился. Я вот учился на эконом факе с/х академии.
UFO just landed and posted this here

Ну так вы и не джун, я полагаю.

UFO just landed and posted this here

Да даже парсер http протокола не задача для джуна.
А сервер это абстракция над ним и определённо сложнее.


Или ваше "реализует" подразумевает "склеить существующие решения"?

UFO just landed and posted this here
UFO just landed and posted this here

А вот если я буду делать что-то более сложное, то, внезапно, узким местом может оказаться совсем не сокетная система.

с написанием на типах простого http эксперты не справились.

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


Каким экспертом нужно быть чтобы пройти вводный курс по языку и посмотреть на examples в репе?

а джуну говоришь "у нас такая парадигма: сперва пишем тест, затем пишем код" и он ей следует.

Следовать-то следует. В том смысле, что он пишет тесты до того, как пишет код. Но откуда взять уверенность, что он пишет правильные тесты?

Справедливости ради, уверенности, что джун пишет типы правильно, тоже нет. Как-то раз встретил код на typescript, где все ошибки типизации глушились через as SomeType

как-то раз встретил код на typescript, где все ошибки типизации глушились через as SomeType

На это достаточно легко настроить статический анализ.

Это тоже можно обойти, например, через любую функцию возращающую any (например, пара JSON.parse/JSON.stringify). Мой поинт в том, что аргумент ад джунум плохой, потому что любой инструмент можно использовать неправильно

любой инструмент можно использовать неправильно

Но неправильное использование некоторых инструментов проще заметить, чем других. Вот мне проще определить неправильные типы, чем некорректные юнит-тесты.

и пишет тест, который ничего не тестирует

UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here

Да, это лучший (я не иронизирую) аргумент против тестов, но именно поэтому некий Джон Хьюз придумал… ну, вы в курсе :)

UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here

Вовсе нет, просто пример утрированный. Тестами нельзя ничего доказать, типами (настоящими, не теми, которые, например, в Java, и даже не теми, которые сегодня есть в Хаскеле) — можно.


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

UFO just landed and posted this here
UFO just landed and posted this here

Я понимаю правильно, что вы решили таки проблему останова?
Пусть даже в "элементарном" случае HTTP сервера.

UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here

HTTP это текстовый протокол. Соответственно ответ сервера выглядит как строка начинающаяся как GET HTTP 1.1\r\n..., поэтому и сериализация в строку совершенно нормальна.

Если надо объяснять, то не надо объяснять.

UFO just landed and posted this here

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

UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here

Ну парсер, по типам же видно. Пытается получить всё до \r\n или двоеточия, считаывает ключ (всё кроме двоеточия, за которым следуют опциональные пробелы), затем по той же схеме считывает значение (всё, кроме символа перевода строки). Потом упаковывает tolower ключ и значение, результата — тапл двух строк.


В случае ошибки видимо возвращается Left "ParserError" или что-то схожее.


Какие нормы "программизма" правда нарушает непонятно. Взяли спеку, посмотрели что там написано "чтение заголовков", прочитали заголовки.

UFO just landed and posted this here

Откуда я знаю какой файл? Я читаю то что вот тут кусочек скинут, и рассказываю что он делает, если у вас с восприятием ML проблемы. Я сам пару месяцев как начал изучать, но для того чтобы без проблем понимать базовый синтаксис этого хватает.


Парсит он, очевидно, входящий стрим символов. Потому что SRP, и парсер заголовков ничего не знает про то, прислал ему клиент данные или мы например в файл сохранили HTTP запрос и теперь его парсим (такой функционал, например, есть в фиддлере).

UFO just landed and posted this here
если это он же используется для парсинга Request, то я сразу уволил бы того кто это написал.

Почему? Что тут не так?

UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here
то наткнувшись на этот заголовок, можно сразу выбрасывать код в мусорку, а его писателя увольнять.

Почему вдруг?

UFO just landed and posted this here

Я в таких случаях склонен считать, что говорящий это просто не умеет объяснять.

UFO just landed and posted this here
так исторически сложилось что false обозначают нулём, а true обозначают не нулём.

Ну не знаю. По мне, "так исторически сложилось", что true и false обозначают разными вещами, а 0 там или 1 — мне более менее все равно.


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

UFO just landed and posted this here

Это меня не удивляет, учитывая вашу нелюбовь к типам вообще.

UFO just landed and posted this here

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

UFO just landed and posted this here

Я же не говорю, что это ваш интент, это скорее небольшая рефлексия.

UFO just landed and posted this here
что на простой задаче у них началась война safe'ры против unsafe'ров

Да что вы говорите. Простые вебсерверы на расте, написанные без всякого unsafe и очень sound кодом — существуют. Война началась как раз там, где, судя по всему (хотя я не вдавался в мелкие детали) ради выжимания последних процентов быстродействия (что даже в вашей системе координат простоты никакой «рядовой джун» не сделает) код стал таким себе.
>Их типы реальных задач и не решают.
Ну блин… типы не об этом. В остальном — ну где-то вы правы, в главном — что реально мощные типы пока далеки от практики на сегодня.
UFO just landed and posted this here
и когда вам говорят: не противопоставляйте типы и тесты, вы же не слышите
WAT?!!! Ведь именно автор и начал пытаться противопоставлять тесты типам и на основе этого пытаться доказать, что типы бесполезны!
UFO just landed and posted this here

Ткните пальчиком, где именно?


А то в одном комментарии жалуются, что я, якобы, вообще тестирование не упомянул, в другом — что я, якобы, противопоставляю тесты типам, очень хочется понять: что же я такое писал.


Спасибо.

Извиняюсь, настолько обчитался комментариями, что некоторые из них начал воспринимать как часть статьи.
UFO just landed and posted this here
Ну, где я-то противопоставлял? Я лишь все время подчеркиваю, что тесты имеют ограничения. По той простой причине, что это не способ доказать что-либо, а лишь проверить, что частный случай работает (и да, я тут тоже про типичные современные языки, где даже property based тестов обычно нифига не найдешь, а есть лишь обычные unit, с одним набором параметров).
UFO just landed and posted this here

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

UFO just landed and posted this here

Это не похоже на требования к программе. Советую за этим сходить в кафе или бар поблизости.

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

Ну смысл как раз в том, что уровень неудобства давно сдвинуто с "неудобно в типах выражать хоть что-нибудь" к "неудобно работать с зависимыми типами".


Один из хороших примеров, когда я делал структуру


public class IdOf<T> { 
   public Guid Value {get; } 
}

с фантомным типом T. Все айдишки были гуидами поэтому можно было передавать вместо айди юзера айди заказа например. А с этой штукой такие проблемы стали ошибкой компиляции. Вот было у вас:


public User GetUserById(Guid userId) => 
  users.SingleOrDefault(x => x.UserId == userId)

и вызывали вы


GetUserById(user.OrderId)

Работает, но неправильно.


После фикса становится:


public User GetUserById(IdOf<User> userId) => 
  users.SingleOrDefault(x => x.UserId == userId.Value)

ну и соответственно


GetUserById(user.OrderId)

Падает с ошибкой "ожидался айди юзера, а передан айди заказа".


Вот так очень простой тип на 4 строчки решает довольно часто встречающуюся проблему "случано отдали не ту айдишку". Какие тесты тут бы помогли? Ну, наверное какие-то написать можно, но наверняка большинство скажет "тут нечего тестировать, нужно просто быть внимательнее".


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

Ну, я бы сказал, что уже приведенный ниже пример с алиасами из скалы делает что-то примерно похожее. Так что в итоге «неудобно» — это совсем массовые языки, и не очень новые. Java типичный пример — типы есть, но выразительные возможности — так себе.

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

Не всегда получается. Хотя я пытаюсь заменять Java на Scala, и мне на сегодня нравится результат.

Скала довольно неплоха, особенно с выходом дотти. Поковырял его — весьма неплохо. Хотя ML синтаксис мне всё больше нравится, но не думаю что кто-то сможет сделать с ним популярный язык. А жаль.

Эх, блин. дотти… нам до версии scala 2.12 как до луны пока… Хадуп живет на Java 1.8. Задачи по его переводу на Java 11 уже много лет открыты в JIRA.
А что, дотти (0.21.0-RC1) уже применима в реальной жизни? Я догадываюсь, что к спарку ее хрен прикрутишь, это его самого нужно будет пересобрать (что как минимум лениво), но хотя бы для изучения уже можно брать?

Для изучения можно. Стабильная версия должна выйти до конца года.

UFO just landed and posted this here

Вы юнит-тестами базу тестируете?

UFO just landed and posted this here

Вы в курсе о концепции mocks и прочем?
Чем вам помогут тесты на тестовой базе?
Как вы убеждаетесть, что на тестовой работает и будет на проде?

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

UFO just landed and posted this here

Как вы доказываете, что ваш тест на тестовой базе тестирует продакшн сценарий?
Или снова будет "а у меня работает, значит правильно"?

UFO just landed and posted this here

Ну то есть ваши тесты ничего не дают кроме "у нас всё ок, в тестовом докере".
Где "консистентность" вашей тестовой базы и реального мира продакшна?

UFO just landed and posted this here

Это заявление об идентичности бд очень актуально, когда прод бд на 1ТБ. Как только Вы берете срез БД вместо целой БД — идентичность вылетает в трубу. М вместо одной вещи (тесты) приходится начинать думать о двух (+ правильный ли срез БД мы взяли). Я уж не говорю о том, что время выполнения теста на большой бд может быть неприлично большое.
В общем, типичное непонимание пирамиды тестов и юнит-тестов, в частности

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

Вообще типы — это как раз про задачи такого уровня.

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

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

Ну вот. По большому счету это все, что умеют типы в современном массовом строго типизированном языке. Ну то есть, если вы скажем про… Java, то вы скорее правы, чем нет. Правы — потому что система типов Java проблему написания джуном веб сервера не решает (хотя и помогает например рефакторить очень сильно). Но с другой стороны, система типов Java — она уже очень старая (ведет свою историю с версии 1.5, 2004 где-то), т.е. прошло уже 15 лет, и с другой — сразу с рождения была legacy, потому что сделана с целью получить совместимость с тем, что было до 1.5.

Ну то есть, современные нам массовые языки — они не так чтобы очень уж хороши в этом плане. Сами по себе — по сравнению с языками со слабой типизацией все как раз нормально. Те которые хороши — пока не массовые. Про Rust не скажу, так как просто не знаю.
UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here

Не обязательно. Например, при минимальной поддержке IDE после того как я нажму двоеточие у меня автоматически напишется return a + b, и мне останется только дописать код выше этого return'а.


А вот в тесте точно придется дублировать код, описывая expected.

Не совсем понятно при чем здесь сравнение ошибки компоновки функций и ошибка в логике функции?
UFO just landed and posted this here
UFO just landed and posted this here

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

UFO just landed and posted this here
Вот только покрыть тестами то, что покрывается типами на порядок дороже.
UFO just landed and posted this here
Ну что ж. Я вижу из этого только один выход.

Всё что возможно — покрывается типами. И только то, что невозможно покрыть типами — покрывается тестами.
UFO just landed and posted this here
то есть 10% покрываем типами
90% тестами

Откуда цифры?


при этом на покрытие типами 10% проблем мы тратим примерно столько же сил как на покрытие тестами 40% проблем

Откуда цифры?


Собственно, у вас уже спрашивали, сколько тестов надо, чтобы покрыть то, что покрывает явная типизация fib.

UFO just landed and posted this here

Ну то есть эти цифры означают, что у вас (и, возможно, вашей команды), на используемых вами яхыках (каких, кстати?) получается покрыть типами в девять раз меньше, чем тестами, и в четыре раза меньше. Допустим.


Почему вы думаете, что у других (особенно когда у них другой тулчейн) так же?

UFO just landed and posted this here
потому что другие так же пишут программы живущие в реальном мире: ходящие по http, обращающиеся в БД итп

Ну да, я вот пишу. И у меня совершенно другие пропорции, нежели у вас.

90% чего вы покрываете тестами?


Ну то есть когда говорят, что 90% яблок зеленые, это значит что есть N яблок из 0.9*N штук из них имеют зеленый цвет.


Что вы взяли за N?

то есть 10% покрываем типами
90% тестами

50% типами за 10 у.е. усилий и 50% тестами за 50 у.е. усилий. Т.Е. типы в 5 раз более эффективные на единицу усилий.

Говорю с опыта поддержки кучи проектов на JS, TS и C#
при этом на покрытие типами 10% проблем мы тратим примерно столько же сил как на покрытие тестами 40% проблем

А сколько у вас строк тестов в среднем на 1000 строк кода? По типам оверхед обычно где-то ~5%

UFO just landed and posted this here
большую часть проблем выявляют тесты
Потому что тесты работают явно, а типы — нет. Тесты запустились, красные — пофиксил. Видишь их пользу.

И типы работают пока пишешь и пользы не видишь. Потому создаётся такая ложная иллюзия меньшего влияния.
UFO just landed and posted this here
UFO just landed and posted this here

(Мама, он первый начал!)


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

UFO just landed and posted this here
ну мы пришли в споре к тому что Вы согласились с моим базовым утверждением: тесты решают более обширный круг проблем нежели типы.

Не было такого. Более того, я вам неоднократно указывал на обратное.

UFO just landed and posted this here
я трактую это так что, B согласен с утверждением

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


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


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

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

Ну, с этой-то диаграммой и я согласен.

UFO just landed and posted this here
навскидку: типы не решают алгоритмических проблем.
тесты решают.

Навскидку: тесты не решают проблемы рефлексии по коду. Типы — решают.


когда программист печатает код, он конечно делает опечатки (то что решают типы)

Типы решают (далеко) не только опечатки.

UFO just landed and posted this here
навскидку — типы ухудшают рефлексию по коду

Так. Начнем с простого вопроса: что вы понимаете под рефлексией? Потому что я под ней понимаю возможность программно получить информацию об объектах/функциях.

навскидку — типы ухудшают рефлексию по коду
У вас странная, неполная типизация. Так какой тип вернётся? И вам кажется нормальным плюсовать комплексное число и флоат? Какой это язык вообще?
UFO just landed and posted this here
что значит неполная?

полная. на вход может прийти float, int или комплексное число

на выходе тоже будет оно же

Значит, что я не знаю, что будет на выходе. Ну, к
примеру что я получу на выходе?
sum(int, float)


Да я даже тут не знаю, что получу на выходе в вашем примере:
sum(int, int)


И это вы называете типизацией?
В любом случае такое (если очень нужно) можно было бы записать как-то так (псевдокод)

type Number = typing.Union[float, int, complex];

def sum(a: Number, b: Number) -> Number:
     return a + b


А вообще у вас этот код слишком динамический. Вместо того, чтобы сужать типы — вы их зачем-то расширяете. Это вообще довольно сомнительная типизация
UFO just landed and posted this here
то есть Вы предлагаете писать три функции сложения, отдельно для каждого типа?

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


а complex и int вместе сложить нельзя?

А результат будет какого типа?

UFO just landed and posted this here

Ну вот это и должно быть видно из сигнатуры.

UFO just landed and posted this here
То есть сложили голубцы с воробьями? В трёх голубцах три воробья, значит два голубца и два воробья — это где-то пять воробьёв, так?
UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here

… а теперь вы у результата пытаетесь взять мнимую часть.

UFO just landed and posted this here
текст над стрелочкой докажите

Кстати, о доказательствах. Вы покажете, как можно тестами гарантировать, что из функции всегда возвращается итератор?

UFO just landed and posted this here
тестами можно это гарантировать для покрытых тестами диапазонов.

А типами (в соответствующей системе) — для всех.


мы с вами (с маленькой буквы, чтоб не говорили что ники перепутал) выше выяснили, что 100% доказательств не бывает.

Нет, не выяснили.


теореме Райса, которая гласит: "100% кода нельзя доказать"

Теорема Райса гласит не это, но не суть.


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

UFO just landed and posted this here
нам надо получить уверенность за цифру близкую к 100% кода что мы написали.

Я не знаю, что нужно вам. А мне нужна конкретная проверка. Тесты ее могут или нет?

UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here

Утверждение "все задачи нельзя", прямо скажем, неоднозначно.

UFO just landed and posted this here

Ну об этом, собственно, и речь: нафига мне весь пул задач? У меня есть мои конкретные, и они доказуемы.

UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here

… обещаете перестать ссылаться на эту теорему?

UFO just landed and posted this here
lair не согласен, а я вот, допустим, согласен. Типы на практике не покрывают всех задач тестирования, которые могут покрыть автоматизированные тесты. А авто-тесты не покрывают всех задач, которые могут быть покрыты ручным тестированием.

Зато типы значительно дешевле тестов, покрывают самые главные задачи и дают дополнительные плюшки, которые не могут дать тесты — к примеру корректное автодополнение.

И только когда мы выжали всё из типов как более удобного инструмента — остаток покрывается авто-тестами.

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

На практике ещё на этапе типизации уходит под 70% ошибок, но она настолько дешёвая, легка и прозрачна в обращении, что этого просто не замечаешь.
lair не согласен, а я вот, допустим, согласен.

Тут главное — уточнить, с чем согласен или нет.

UFO just landed and posted this here
При чём тут вообще опечатка? Опечатки и в JS не будут работать — там будет ошибка.

Типы — это аналог "Проверки размерности" в математике. У тебя есть формула ускорения. Если размерность возвращается корректная «м/с2», значит формула с большой долей вероятности написана правильно.

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

И сейчас при адекватной типизации в итоге решение сводится к какому-то типу. Если свелось — значит оно корректно. И только опечатка может стать причиной ошибки. А вы сами утверждаете, что опечатки — это не так важно.
UFO just landed and posted this here
типы решают только проблемы опечаток в идентификаторах.

Нет.

UFO just landed and posted this here

Аргументы уже сильно больше одного раза были, и вы на них просто не ответили. Смысл их повторять?

UFO just landed and posted this here
UFO just landed and posted this here
основную проблему которую показывают типы

Нет.


когда str и int перепутались. но этой проблеме Ларри Уолл нашел решение четверть века назад

… и как же он предлагает решать проблему деления строки на число?

UFO just landed and posted this here

Эм. Ну то есть в каждом (публичном) месте, где мы ожидаем число, надо написать код, который приведет строку к числу (а вдруг нам передали строку вместо числа)? И бросить исключение? И обработать его уровнем выше?


Но зачем, если можно сказать "я принимаю только числа", и быть уверенными, что ничто, кроме числа, вы получить не можете?


Вот вам развлечение: приведите строку "123,45" к числу.

UFO just landed and posted this here
это делает сам язык.

А можете код показать?

UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here

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

UFO just landed and posted this here

И как сделать так, чтобы, если в $a не число, была ошибка?

UFO just landed and posted this here
включить варнинги — первый уровень (поматерится но посчитает)

Включить где?


но второй уровень в жизни обычно никому не нужен и никто так не делает

То есть мы получаем от внешнего пользователя "abc" и ^_^quotquot^_^, и возвращаем 1?

UFO just landed and posted this here
в коде, написав use warnings;

Внутри функции? Снаружи? Где угодно?


да и для реального мира это очень хорошее поведение.

Garbage In — Garbage Out — хорошее поведение? Я с вами не соглашусь. Если у меня некорректно формируются суммы в приходящих документах — это не хорошее поведение.


только у вас первоначально было 1 как одно из слагаемых

Привет парсеру.

UFO just landed and posted this here
там где нужно. распространяется на скоуп в котором применено.

А если применить в скоупе, который снаружи функции, на функцию распространяться будет?


итп

… так что с суммами в документах-то? Нормально, что они теряются? Хорошее поведение?

UFO just landed and posted this here
на весь модуль в котором примените :)

То есть одна функция может поменять поведение всего модуля?


Вы опять об опечатках?

О нет, я о внешних системах. Шлет вам внешняя система документы, а вы их парсите и складываете к себе в учетную систему. В каждом документе — два поля (сумма до налогов и налоги, суммы с налогами нет), вы к себе пишете сумму с налогами (складывая одно с другим). Внезапно вместо налогов начинает приходить строка Applied, в том самом поле.


Что получится с вашими правилами сверху?

UFO just landed and posted this here
Вы же применить его ВНЕ функции собрались?

Нет, я спрашивал о том, как определяется зона действия use warnings.


А, я понял. У вас не бывает функций внутри функций, да?

UFO just landed and posted this here

Я все-таки не понимаю, что будет, если написать use warnings внутри функции.

UFO just landed and posted this here
в этом месте лучше упасть по Вашему?

Ну да.


по моему лучше посчитать то что можно

А что тут можно посчитать?


проинформировать мониторинг о проблемах

Ну так если упасть — мониторинг автоматически проинформируется.


Более того, еще и сопряженная система проинформируется, что особенно хорошо.

UFO just landed and posted this here
пользователь получит 500

Неа. Пользователя там нет, там есть клиентская система. То, что она получит 500 (на самом деле — 400, но не суть) — это хорошо, потому что теперь все знают, что что-то пошло не так.


А пользователь не получит кривых данных. Что тоже хорошо.


всё что можно стоит посчитать

Там нет ничего, что можно посчитать. Было два числа, стало одно.


то такая ситуация (отсутствие валидации ввода)

О! Валидация данных! Это внезапно, потому что раньше речь шла о "для реального мира это очень хорошее поведение". А теперь, внезапно, мы что-то валидируем.


Значит ли это, что это не хорошее поведение?


остальное тестами покрыто

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


Проще говоря, сколько вариантов входных данных будет для теста функции?


def create_document_from(net, tax):
  return Document(total=net+tax)
UFO just landed and posted this here
тут выше были рассказы про сваггер — отлистайте.

Какой-такой сваггер? Нет никакого сваггера. Есть пропьетарный формат.


на самом деле до этой функции не дойдут такие данные

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

UFO just landed and posted this here

Ненене, давайте на конкретных примерах. Вот есть "алгоритм", который принимает от недоверенного источника данные. Для простоты, этот алгоритм — это сразу вон та функция, потому что нет никакой разницы, где мы в стеке находимся. Ну в конце концов, выставлена у вас эта функция как публичный API модуля, мало ли.


Так вот, сколько вариантов входных данных будет?

UFO just landed and posted this here
давайте на конкретных примерах.

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


И да, забегая вперед: мне искренне все равно, к чему вы приравняете мое нежелание говорить про actix-web, потому что у меня нет ни информации, ни мнения по этому поводу.

UFO just landed and posted this here
UFO just landed and posted this here
где надо валидируем, а где не валидируем — это хорошее поведение

Ага, значит, все-таки, валидируем. И, судя по всему, валидируем не описанными выше встроенными средствами языка (потому что, цитирую, "никто так не делает").


самое крутое что код похож на человеческий язык.

Я не очень понимаю, что мешает "человеческим языком" типы указывать.

UFO just landed and posted this here

Ненене, давайте не забегать вперед, и ставить галочки.


Галочка первая: принцип "получили что угодно — сложили с чем угодно" — не хорошо в реальном мире (в общем случае).


Галочка вторая: для того, чтобы гарантировать выполнение первой галочки, вы предлагаете писать код (вопреки ранее сказанной фразе "это делает сам язык").


Так? Или нет?

UFO just landed and posted this here
код писать надо

Ага, прекрасно, по двум пунктам согласились.


Теперь следующий вопрос: как в языке, в котором "решена проблема перепутанных строк и чисел", выглядит проверка на то, что оба входящих параметра — числа?

UFO just landed and posted this here
man stdlib:atoi/itoa и всякие dtoa?

Не, не man. Код покажите.


алгоритм задания звучит так:

… какого задания?

Я не очень понимаю, что мешает «человеческим языком» типы указывать.
Это Пёрл то человеческий язык?

… а я вот удержался и промолчал.

UFO just landed and posted this here

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


Покажите тем же людям, вот эту строчку, пожалуйста. При том, что да, мне кажется, что я осилил.

UFO just landed and posted this here
когда опечатался в названии переменной, её тип получился undefined и он несочетаем с тем что ожидает функция или выражение
Я просто не понимаю, о чём вы говорите. Оно ведь просто не скомпилируется. С типами или без.

Если будете продолжать вести дискуссию в том же духе, то я просто буду отвечать, что тесты нужны, чтобы раз их запустить, понять их бессмысленность и никогда больше не запускать. Ну чтобы соответствовать вашему уровню
UFO just landed and posted this here
применение таких языков например в вебе — растёт из за непрофессионализма
Интересно, какой язык порекомендует наш якобы «проффесионал» вместо джаваскрипта в вебе?
языки, которые компилируют код нужны в современное время в очень, очень, очень узких нишах

Шта? А я и не знал, что пятнадцать лет работаю в очень-очень-очень узкой нише… LOB-приложений.

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

UFO just landed and posted this here

Вот вам еще один пример проблемы, которую решают типы.


//можно или нет?
foreach(var user in GetUsers())
{
  //можно или нет?
  user.Block();
}

Типы отвечают на эти вопросы прямо здесь и сейчас. Тесты — не отвечают (потому что GetUsers предоставлена библиотекой).

UFO just landed and posted this here

Конечно, нет.


object GetUsers()
{
  if (youHaveNoRights)
    return null; //это не опечатка
  else
    return new [] {"user1", "user2"}; //и это тоже
}
дальше Вы задали риторическое «и что?». его можно рассматривать как два варианта:


Есть третий вариант: вы сказали какие-то утверждение, но я ожидал от вас какого-то вывода. Если я напишу здесь: «куры летают», а вы в ответ спросите: «и что?» — вы тоже «проиграли в споре»?

дальше Вы
Обращайте внимание на ники, мы разные люди.
UFO just landed and posted this here
Для того класса задач, которые могут покрыть типы — тесты значительно худший инструмент. От того, что тесты могут покрыть какие-то другие задачи они не становятся лучше в тех задачах, в которых они хуже.
Примеры, что будет, когда ее нет — уже были выше, например, передали uuid заказа, вместо uuid пользователя.
Да, мы натыкались на то, что система типов от этого не страховала. После первого же бага разделили их на условные UserId и OrderId.
UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here
Уже на этом этапе вы упретесь в тот факт, что число возможных вариантов запроса практически бесконечно, потому что http далеко не простой. И покрыть их все тестами с большой вероятностью невозможно.
UFO just landed and posted this here

Пример можно увидеть, с доказанной корректностью хотя бы на уровне 2хх — 4хх?

UFO just landed and posted this here
UFO just landed and posted this here

Мы уже с вами(?) обсуждали юнит-тесты и проблемы с переполнением?
SSL тоже работает, даже корректно, что не отменяет проблем.

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

UFO just landed and posted this here
UFO just landed and posted this here
то, что вы сможете описать на типах — будет как правило работать, если скомпилировалось

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

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

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

И первое, и второе — далеко не идеальны. Но тезисы о том, что типы — это «серебрянная пуля» для абсолютно всего — я пока что слышал только от вас.
Насколько я понимаю, вы же не пишете на строго типизированных языках?

За деньги — нет. Но я профессионал (мню себя таковым), и не могу себе позволить игнорировать тренды. Я даже на джаваскрипте иногда пишу, что уж тут говорить.

А тогда откуда вывод, что это вера? Я вот пишу — и для меня эффект вполне заметен. Я готов поверить, что вам типы не дают эффекта, но тут же как всегда — «термодинамика — палка о двух началах» — может не дают, потому что не могут, а может — потому что вы готовить не умеете, или скажем задача не слишком простая на сегодня для типов? Готовить пока сложно, тут даже вопросов нет.

Условный практический пример — если у вас часть кода скажем SQL база, то понятно что внутри нее слабая типизация. И на границе с ней есть проблемы, потому что никто не сможет помешать DBA с той стороны сделать ALTER COLUMN, и поменять тип — и внезапно вместо числа прилетит строка.
то, что вы сможете описать на типах — будет как правило работать, если скомпилировалось

для меня эффект вполне заметен [..] или [..] или [..] или [..]

Вам не кажется, что «будет, как правило, работать» и «эффект заметен» с миллионом оговорок — это не совсем диффеоморфные утверждения? «Вера» я говорил про первое. Со вторым, так-то, в принципе, согласен.

Это было в разных комментариях поэтому про разные языки, если что. Эффект уже «вполне заметен» на уровня Java, где типы конечно имеются, но мощность системы типов все еще достаточно мала. В скале уже ближе к «скомпилировалось — работает».

Ну то есть — то что я в этой частной убогой системе типов могу описать с моим уровнем квалификации за разумное время — то уже и работает. И по сравнению с javascript эффект заметен еще как. Как-то так.
Полностью поддерживаю. Ранее писал на достаточно простом языке с динамической типизацией, 4 года с копейками. Перешел на котлин год назад — эффекту не нарадуюсь, работать с кодом стало гораздо проще. Что с чужим что свой писать и поддерживать.
Если силы потраченные на сильную типизацию потратить на написание тестов, то результат будет лучше!

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

Просто потому, что если б тратили — вы бы пример про это приводили, а не про функцию sum (про которую вам уже раньше в комментариях всё объясняли, и да, типы в этом случае не длинные и не сложные).
UFO just landed and posted this here
какую пользу тут принесло указание типов? никакой

Серьезно?


То есть тот факт, что вызывающий код, который попробует туда запихнуть строку, при разумном тулинге сразу получит подсветку ошибки — это не польза? Равно как и то, что сделав for v in fib(5), тому же тулингу известно, что vint, и можно делать соответствующие подсказки — это не польза?


Я вот сам пишу на питоне, и при прочих равных предпочту размеченный код неразмеченному — просто потому, что мне будет проще с ним взаимодействовать.


тесты и типы решают в общем одну и ту же проблему — верификация написанного кода

Типы решают не только эту проблему.

UFO just landed and posted this here

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


Ну то есть объясню: я пользователь функции fib, я ее из модуля импортировал. Мне не надо ее писать. Мне не надо ее править. Мне не надо гарантировать, что она правильно работает. Но мне хотелось бы, чтобы мне было удобно с ней работать. Как мне в этом помогут тесты?

UFO just landed and posted this here
или Вы типы не применяете в вызывающем коде?

Применяю, я даже пример выше написал, как именно. И именно здесь у меня вопрос: как мне тесты помогут их заменить?


для вызывающего кода тесты так же важны для вызываемого

Не понимаю этой фразы. Тесты на какой код? На вызывающий или на вызываемый?

UFO just landed and posted this here
тесты здесь заменяют типы здесь

Нет, не заменяют. Я еще раз говорю: мне от типов нужно, чтобы IDE мне правильные подсказки делала. Как вы этого добьетесь тестами?


а тесты могут.

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


типы — это решение более узкого спектра задач по валидации кода.

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

UFO just landed and posted this here
у нас с Вами разные цели.

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


А если лично вам для ваших задач типы пользы не приносят — ну и ладно. Но почему вы из этого делаете вывод, что они никому не приносят пользы?

UFO just landed and posted this here
UFO just landed and posted this here

Ничего не делаю. А должен?

UFO just landed and posted this here

Я вам повторю второе предложение комментария, на который вы отвечаете: "а должен?"


Я где-то говорил, что моя IDE ловит эти ошибки?

UFO just landed and posted this here

Ну да. И показывает. Я же не говорил, что она показывает все проблема, правда?

проблему которую видят (надеюсь) все

Вы уверены, что все видят одну и ту же проблему?


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

Кстати на расте это можно выразить уже сегодня:


fn sum<M: Unsigned, N: Unsigned>(x: M, y: N) 
-> Box<UnsignedGreaterThan<M>> {
   ...
}

И написать a — b уже не выйдет, будет ошибка компиляции

UFO just landed and posted this here

Ну выразите то же самое в типах, как будто это сложно:


fn sum<M: Signed, N: Signed>(x: M, y: N) 
-> Box<If<Is<Positive, N>, UnsignedGreaterThan<M>, UnsignedLessThan<M>> {
   ...
}

Выразили в типах? Выразили.

UFO just landed and posted this here

Выше написано Signed, если вы вдруг не заметили.

UFO just landed and posted this here

Ну зависит опять же от свойств которые вы хотите выразить.


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


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

UFO just landed and posted this here
программист делает алгоритмическую ошибку.

Делает


единственный шанс выявить это в типах — написать эталонный алгоритм в коде типа.

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


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

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

UFO just landed and posted this here
код этого "автомата" который "автоматически" кто пишет?

Ну компилятор пишут разработики компилятора, очевидно.


у питона кастрированная система документирования кода, у жс её вообще нет

А где хорошая система?

UFO just landed and posted this here
что и тип описывает компилятор?

тип — это требование к коду, которое вам нужно. Очевидно, это информация, получаемая от клиетов, а не от компилятора.


А по типам генерировать код компьютеры уже давно умеют, с разморозкой.

UFO just landed and posted this here

Ну вот например у нас челик задал вопрос, почему такая композиция работает


f1 :: a -> Maybe b
f1 = undefined

f2 :: b -> Maybe c
f2 = undefined

f3 :: c -> Maybe d
f3 = undefined

fn :: d -> Maybe z
fn = undefined

comp'' = f1 >>> f2 >>> f3 >>> fn

А оказалось, что компилятор вывел что функции населены исключительно const None.


Ну и тот же id например компилятор может из сигнатуры вывести. Я понимаю, что это простые примеры, но на них-то всё работает)

UFO just landed and posted this here
UFO just landed and posted this here
выход за пределы границы массива — примитивная задача.

Да?


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


Foo(Bar b) => return _bazs[b.BazIndex];
UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here

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

UFO just landed and posted this here
как это невозможна, если мы это поведение тестируем?

Напомню задачу: надо доказать, что код никогда не выходит за границу массива.

UFO just landed and posted this here
UFO just landed and posted this here
остаётся варьировать вероятностями "вероятность того что не выйдет за границу большая, или маленькая"

Окей, как (с помощью тестов) доказать, что вероятность выхода за границу не превышает 0.001?

UFO just landed and posted this here

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

UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here

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

UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here
из рантайма проверку удалять мы не имеем права

Имеем, конечно, если можно на этапе компиляции доказать, что она избыточна. Что, кстати, компилятор C# и делает регулярно (как раз с проверками границ массивов).

UFO just landed and posted this here

Вы? Возможно. А я стараюсь следить за тем, что я спрашиваю, и на что отвечаю.

UFO just landed and posted this here
выше есть мой код "как раз с проверками границ массивов"

Вот этот код?


def get_value(array, index):
  if index >= len(array):
    return
  if index < -len(array):
    return
  return array[index]

Вы хотели другого переполнения, как выяснилось integer

Вы меня с кем-то путаете. Переполнение чисел меня не интересует.


А к приведенному вами коду у меня есть очень простой вопрос: как с помощью тестов доказать, что он никогда не выдаст ошибку "обращение по некорректному индексу"?

UFO just landed and posted this here
а как с помощью типов это доказать?

Не ко мне вопрос.

UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here
Если вы можете в компилтайме это доказать, то в рантайме у вас будет нулевой оверхед [...]

В воздухе явственно запахло макросами.

UFO just landed and posted this here
Если вы можете в компилтайме [...], то в рантайме [...], и компилятор вырежет нафиг все эти проверки

Ну вот же :)

UFO just landed and posted this here

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

UFO just landed and posted this here
Теория типов.
Утверждаете, что она не работает? Ждем опровержения, а пока у вас тесты даже целочисленное переполнение не могут словить и приходится в рантайме null'ами баловаться.
UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here

Теорема Райса говорит, что существуют программы, для которых нельзя доказать интересующее вас свойство. Например, если мы попытаемся доказать что программа на питоне никогда не кидается TypeError, то:
Для каких-то программ мы сможем это сделать.
Но найдутся такие, для которых доказать это мы не сможем.

UFO just landed and posted this here
UFO just landed and posted this here
Например, наличие счётного числа тривиальных алгоритмов (по одному на программу, где каждый алгоритм тупо проверяет номер исходный текст на соответствие), проверяющих данное свойство для каждой из всех возможных программ, не противоречит теореме Райса

Не, ну тут надо осторожнее. Если обладание данной программой данным свойством невыводимо — вы какой "тривиальный алгоритм" подберете?

теорема Райса говорит что любой (== весь) код доказать нельзя
Нет. Вы неправильно поняли. Не весь код. Существует код, который доказать нельзя, а значит не весь код можно доказать. Но есть множество кода, которое можно доказать
UFO just landed and posted this here
UFO just landed and posted this here
НЕ ВЕСЬ == НЕ ЛЮБОЙ == ЛЮБОЙ НЕЛЬЗЯ
Не все машины чёрные == не любая машина чёрная == любая машина не чёрная

У вас прекрасная логика!
UFO just landed and posted this here
нет не слился, вот вам код, проверяющий переполнение

По-моему, это не переполнение, а выход за границы массива. Нет?

UFO just landed and posted this here

Почитайте, что такое переполнение (integer overflow)

UFO just landed and posted this here
Сложение его переполняет, например. Или умножение.
Но вы без теста словить его не сможете, а с тестом тоже не факт, что получится.

Я не очень понимаю, к чему тут этот код, и о чем вы говорите.

UFO just landed and posted this here

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

UFO just landed and posted this here
я уже отвечал — так же как и вы в типах

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


Меня просто интересует ваше утверждение, что проверка границ массива — это легко.

У вас нет проверки переполнения «в типах». У вас проверка в рантайме/линтером.
UFO just landed and posted this here

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

… или вот, скажем, есть у нас api.


GET /file/{file_id}

file_id — GUID. Пишем соответствующий обработчик:


public Task<File> Get(Guid fileId)
{
   //...
}

И знаете, что удивительно? При попытке сделать GET /file/0 будет 404. И при попытке сделать GET /file/myfile.jpg — тоже.


Мы не написали ни строчки кода, который это проверяет. Мы не написали ни одного теста, который проверяет, как Get себя поведет, если в него передать не-GUID. Казалось бы, это ли не польза? А если добавить к этому, что из этого же описания обработчика автоматически сгенерился Swagger, который всем потенциальнм клиентам рассказал, что сюда надо передавать именно GUID (и что именно будет в ответе, потому что там File), количество пользы продолжает увеличиваться.


Что веселее, если мы поменяем нижележащий слой так, что он начнет принимать не GUID, а что-то другое, статический анализ скажет нам — здесь больше не работает, поменяйте тип выше. И это — поменять тип — приведет к тому, что все прочее поведение поменяется (будет другая проверка в роутинге, и другое описание в Swagger).


Но это, конечно, очень легко воспроизвести тестами.

UFO just landed and posted this here

… и как вам в этом помогают тесты?


(это не говоря о том, что Swagger, в каком-то смысле, это типы)

UFO just landed and posted this here

"Типы — это просто средство, которое..."


Тесты он не отменяет

Да понятное дело. Я-то спрашивал ровно обратное: как с помощью тестов добиться того, что мне в моем примере дают типы.

UFO just landed and posted this here
дык вам не типы это дают, а валидационный код

Вот только этот валидационный код без типов не работает.


который сгенерирован или рантайм работает по сваггер схеме.

У меня ровно наоборот, сваггер-схема генерится из типов.


этот код одинаков и у Вас и у меня

… у вас этот код — тесты?

UFO just landed and posted this here
результат у нас с Вами одинаковый.

Конечно, нет.


Но давайте сначала определимся, ваш (не важно, одинаковый, или нет) результат — он тестами достигнут?


а качество у меня выше

А это — не доказано.

А рефакторинг там же — тоже не пример? Тот, который в результате наличия типов всегда правильно работает?
UFO just landed and posted this here
если бы рефакторинг был бы не нужен, тесты бы тоже были не нужны.

Вы это сейчас серьезно?

UFO just landed and posted this here
если проект не планируется рефакторить (читай развивать), то после релиза и исправления пула ошибок — зачем ему тесты?

Ну так багфикс же.

UFO just landed and posted this here

Нет, конечно. Багфикс (обычно) меняет наблюдаемое поведение, а это противоречит определению рефакторинга:


Refactoring is the process of changing a software system in a way that does not alter the external behavior of the code yet improves its internal structure.

Есть проекты, которые не рефакторят и развивают.

>именно из за рефакторинга вся эта эпопея с тестами и затевается

Дело в том, что в типизированном языке (Java тут вполне достаточно, кстати), рефакторинг можно зачастую провести полностью автоматически. И код при этом не ломается. И тесты на этот случай вообще не нужны. Так что это ваше утверждение — оно как минимум слишком общее.
UFO just landed and posted this here

Конечно. И что?


По факту прочтения вот всех этих сотен комментариев, у меня складывается ощущение, что вокруг идет война тупоконечников с тори.


Я изначально пытался сказать (сейчас становится очевидно, что — крайне маловнятно): «Да, типы дают гарантии. Подумайте трижды, надо ли оно вам конкретно сейчас конкретно в этом проекте, и если надо обязательно — ну что ж, типуйте наздоровье, но не нужно это делать только потому, что Анкл Фоулер так сказал».


Задача чисто алгоритмическая, [почти] без пользовательского ввода, вычислительная, особенно манипулирующая сложными данными — конечно проще формализуется типами, а не чистым кодом.


В моем же мире, самая частая и сложная логически операция — взаимодействие с внешним миром. В [почти] каждом стейте бизнес-процесса — мы ходим в какой-нибудь 3rd party сервис средней ликвидности. И я собаку съел на том, чтобы реализовать fault-tolerance без типов. И заявляю: так тоже можно, и это работает.


А все разговоры про рефакторинг — это в пользу бедных. Проблемы с рефакторингом решаются декомпозицией, а не типами и очень умным IDE. Как и доказательства валидности операций на мета-уровне (типы в CS — это мета-уровень, MOF был адекватным шагом в направлении привнесения такого способа проектирования в ООП, и я три года участвовал в разработке кодогенератора в/из типов в 2000-2003 в Берлине на стеке Java/Haskell, я представляю себе, о чем я говорю).

UFO just landed and posted this here
  1. «Приятнее» — это вкусовщина :)
  2. Я пишу юнит-тесты для того, чтобы проще было посмотреть, как себя ведет только что написанный кусок кода. Все эти TDD, BDD, DDD — религиозная муть, да.
  3. Угу.

Носите с собой доказательство, что внешний мир ведёт себя так, как ожидается [...]

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


Проблемы с рефакторингом решаются декомпозицией

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

UFO just landed and posted this here
я бы делать этого не стал, потому что тогда мне надо было бы решать две задачи параллельно

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

UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here
теперь больше работы при написании кода, но никакого профита это не принесло

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

Сколько тестов нам придется написать, чтобы быть уверенными, что Фибоначчи работает верно? Тем более, что нас же ведь интересуют не только ситуации корректного входа в функцию, но и некорректного — и она должна каким-то образом среагировать (упасть с исключением, вернуть какое-то значение и по.).


Что интереснее — int на выходе Фибоначчи это не тот int, что на входе. Вас же не смущает, что некоторые функцию из int делают float, так почему не написать в сигнатуры некий класс FibNumber, который позволит нам в дальнейшем четко понимать, что это фиб-число, если, например, нам важно это в каких-то других функциях. 0xd34df00d

Ну так, пишете тесты:


fib 0 == 1
fib 1 == 1
fib 2 == 2
(0..5).map(i -> fib i) == 20


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

Какие-то неправильные тесты ) помимо того, что они ничего толком не тестируют. Всякие переполнения — вообще за кадром. Так и особо хитрый разработчик может заколотить значения для первых 10 членов фиб-последовательности в массив. А то что 20-й не считается — ну, бывает.

UFO just landed and posted this here

А какой http сервер Вы хотите тестировать?
Т.е. вопрос что именно мы проверяем — корректность отдачи кодов ХТТП, корректность данных или вообще у нас HTTP это всего лишь обертка к некоей стейт-машине, с которой мы по этому протоколу как транспорту общаемся и шлем сообщения ?

по роду деятельности пришлось перейти с Perl на Python

Ну так python — это динамический язык, с динамическими же идиомами. А типизация динамических идиом, да и вообще всякий gradual typing — это по сути последние 10-15 лет, естественно, тут бывают проблемы. У языков с изначально статической типизацией таких проблем нет по определению. Так что ваш пример с питоном очень, очень плохой.


Но даже так:


если те же усилия направить на написание тестов, то эффект будет бОльший.

Для добавления типов пришлось видоизменить 1 строчку код: def fib(n: int) -> Iterator[int]:


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

Напишите, пожалуйста, как будут выглядеть покрывающие этот код тесты

… мне в таких случаях интереснее всего, как выглядит тест, который проверяет, что на выходе — всегда итератор.

Хотелось бы всё-таки отделить сильные (но динамические) типы в Python и неявное преобразование в JS. В питоне мы в большинстве случаев словим ошибку конкатенации разных типов при выполнении, а в JS получим 11 вместо 2 и «корректно работающую программу».
UFO just landed and posted this here
UFO just landed and posted this here
строка — это число взятое из атрибута xml

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


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

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


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

Это утрированные примеры. Утрированные. Ну вот представьте себе реквизиты компании. Их миллиард. На каждый чих заводить тип? Но из базы-то придет, хоть умри в клиентском коде — строка.

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

Я нигде не утверждал, что это невозможно.

Я не в этом смысле. Имелось в виду, что на уровне базы вполне себе имеются алиасы для типов. И можно разделить фамилию и имя. Поможет ли это на уровне клиента — далеко не факт, во всяком случае если и поможет — то не сразу. Нужна будет генерация, или ORM, в общем — еще усилия.

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

UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here

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


100% запросов были описаны в коде, через предоставляемый ORM интерфейс, написание хранимок и сырого SQL было запрещено гайдлайнами и анализатором кода.


Всё отлично работало, клиенты довольны, деньги платили.


Проекту про который я говорю уже лет 10. Сколько еще надо подождать чтобы проблемы от неидеальной базы данных проявили себя?

UFO just landed and posted this here

Я уже говоорил про объемы — бд на сотни гигабайт, пользователей — сотни B2B клиентов.


Сколько ещё ждать?

>ORM дистанцирует программиста от необходимых знаний о внутреннем устройстве БД
Это не всегда минус. Как минимум, это некое разделение труда, когда часть ORM пишут отдельные люди. Если у вас условно, несколько баз, и не все они ваши — это может помогать. Не сказал бы, что оно не создает проблем — но оно и решает некоторые.
UFO just landed and posted this here
> в школах изучали внутреннее устройство машины.
Я даже ходил в такую по своей инициативе (Первая детская автотрасса называлось).

>можно и без этих знаний обходиться
Ну, таки понимать, почему и зачем нужна коробка или сцепление, все равно полезно.
UFO just landed and posted this here
UFO just landed and posted this here

Очевидно это всё не нужно, ведь у нас и так работат всё, я вот даже тест написал:


[Fact]
public void DatabaseIsCorrect() {
    using(var context = new DataContext(Resource.DbConnString)) {
        context.Users.Insert(new User(Name = "Alex", Age = 27));
        context.SaveChanges();
        var alex = context.Users.Single(x => x.Name == "Alex");
        Assert.Equal(27, alex.Age);
    }
}
Не совсем понял, как это возможно? Проблема с SQL в основном в том, что в любой момент (в теории) может быть выполнена ALTER TABLE, и поменяться тип данных, название колонки, или что-то еще в том же духе. То есть, если мы код генерируем — у нас в общем случае нет гарантии, что база все еще соответствует тому, что мы сгенерировали.
UFO just landed and posted this here
А. Ну в такой постановке наверное да. Хотя проверить соответствие модели БД — это не всегда так быстро, как хотелось бы. То есть, это отдельный запрос к БД, и далеко не обязательно быстрый на больших схемах.
UFO just landed and posted this here
Ну я так и делаю. Просто прикиньте, схема на 1000 таблиц, таблицы бывают по 500 колонок — это в пределе полмиллиона записей, которые нужно прочитать, чтобы просто достать модель. Ну или 1000 запросов на таблицу. И они не особо ускоряются. Если при генерации (сборке приложения) это не страшно, то при запуске уже бывает неприятно.

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

Ну это слегка не мой случай. У меня база не своя, а без такой таблицы, где вы возьмете хеш от текущего состояния БД, если меняете ее не вы? Только там же — вытащив миллион из ALL_TAB_COLUMNS.

Ну у нас на проектах просто по рукам давали людям которые схему в БД правили руками. Все же понемногу индустрия старается переходить на воспроизводимые сборки: docker (build as a code), infra as a code, db as a code, и так далее.

Не, вы не поняли. БД — это не мой проект. Что они там делают, руками или нет — я не в курсе. Но вот создавать там мне свои таблицы никто не дает — потому что их проект например mission critical, а мой — уже нет. У нас R/O, иногда даже VIEW вместо таблиц, никаких персональных данных, и т.п.

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

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

UFO just landed and posted this here
Про это ниже тоже есть. Впрочем, ALTER тоже бывают весьма и весьма экзотические, с очень непредсказуемыми последствиями.
UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here

Он не позволяет писать SQL (как текст), он позволяет выразить любую конструкцию нижележащего диалекта.

UFO just landed and posted this here
читабельность нулевая
профит нулевой

Для вас? Вполне возможно. А для меня возможность статического анализа — это далеко не нулевой профит. А читабельность… такой же код, как любой другой код на том же языке, чаще всего fluent, у меня проблем не было.

UFO just landed and posted this here

Ну как-как.


Вот типичный пример кода, конструирущего WHERE:


users.Where(u => u.Age != null && u.Age < 15)

Что мы здесь имеем, вот прямо сразу:


  • невозможность выбрать несуществующее поле (неважно, намеренно или опечатавшись)
  • невозможность сравнить число не с числом
  • обязательное определение поведения для null (если не указать первое условие, во втором не сойдутся типы)

На мой личный вкус, вполне себе помощь.

UFO just landed and posted this here
предполагается что SQL всё-таки человек знает, раз такое пишет

Кем предполагается? Мной не предполагается, у меня нет такого требования к разработчикам.


это не профит.

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


крайне сомнительный плюс

Для кого как. Для меня отсутствие ошибки от СУБД в момент, когда мы попробуем сравнить возраст с огурцом — достоинство.


напишите

Что делает оператор USING?


(хотя я, если честно, не понимаю, зачем там вообще WITH, и в чем отличие от обычного джойна с drivers)

UFO just landed and posted this here

Гм. Ну ладно, поверю вам на слово.


var driver = dbContext
.With(c => 
  c.Drivers.Where(d => d.Name == name)
);

driver
.LeftJoin(dbContext.Balances)
.Select((d, b) => b);

)

UFO just landed and posted this here

Вы, конечно, имеете личное право на такую реакцию. Но она меня моего профита от этого кода не лишает.

Жесть, конечно, но получше, чем в строке чистый SQL писать
получше, чем в строке чистый SQL писать

Или похуже. Потому что SQL можно отладить в где-нибудь и копи-пастнуть.


Или вообще ничем не отличается, кроме синтаксиса.


Я за последний вариант.

UFO just landed and posted this here

Спорить о превосходствах того или иного синтаксиса в 2020 — это что-то из области розового гламура :)


Варианты изоморфны же.

UFO just landed and posted this here

Ключевое слово в моем комментарии — «спорить». Вам больше нравится SQL-синтаксис, предыдущему оратору — шарп (или кто там). Но правильного ответа на вопрос «какой синтаксис лучше» — нет.


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


Я практически ушел из руби в Erlang / Elixir даже не потому, что на них в тысячи раз сложнее написать плохой код. А потому, что виртуальная машина эрланга как будто спроектирована для моих задач, и потому, что let it crash и supervision trees — позволяют мне писать в миллиард раз лаконичнее и выразительнее.


Дело тут не в синтаксисе, а в проектировании самого языка. Именно поэтому, кстати, я так низко оцениваю Go, который будто специально спроектирован для написания write-only кода, как только мы выходим за рамки hello world.

UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here

Как-то так.


  • код собрался, то запросы к БД будут корректными
  • эквивалентность SQL-запросов проверяется (и shrink есть)

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

UFO just landed and posted this here

По нормализованному AST, если все осталось, как в предыдущей версии. С тех пор не смотрел, но я этим ребятам доверяю.

UFO just landed and posted this here

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

UFO just landed and posted this here
У меня есть еще более занятное развлечение. Мы генерируем запросы вида WHERE a BETWEEN 'a' AND 'z', например. И они работают — до тех пор, пока в Оракле не построят по колонке a реверсивный индекс. После этого запрос начинает выполнять фулл скан, и время выполнения вырастает до нескольких часов, например, что ни в какие ворота не лезет. Ну т.е., схема не менялась, все вроде как было — но тормоза-а-а-а.

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


Когда я столкнусь с такой задачей, я буду знать, куда копать, спасибо.

Мне вот внезапно стало интересно: а по какому формальному критерию вы разделяете ORM и Active Record?

UFO just landed and posted this here
UFO just landed and posted this here
по идее нет.

По какой идее? Вот определение (PoEAA):


An object that wraps a row in a database table or view, encapsulates the database access, and adds domain logic on that data.

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


зло — автогенерация SQL (SQL-автоконструкторы)
зло — ручная генерация SQL на языке (SQL-конструкторы)

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

UFO just landed and posted this here
переходя от одного определения того же самого к другому.

Я где-то давал другое определение AR? Серьезно?..


Вы уже оперируете ТРЕМЯ языками

Конечно, нет. Я оперирую одним языком — тем, на котором пишу, а то, что я на нем выражаю — это задача, которую я решаю.

UFO just landed and posted this here
чтобы писать WITH на языке конструктора — Вам надо знать SQL

Неа. Мне надо знать, что есть такая операция, а не ее синтаксис в диалекте SQL.


язык запросов к конструктору.

Тоже нет. Это бизнес-задача.

UFO just landed and posted this here
когда мы пишем на любом языке — то время от времени заглядываем в справочник его синтаксиса.

Да, но мы не пишем на этом (SQL) языке, в том-то и пойнт.

UFO just landed and posted this here

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

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


Профайлер скажет: вот конкретно этот вызов занимает внезапно три минуты. А вчера возвращался на наносекунду. И все.


И придется звать DB Architect, который стоит как три девочки по вызову (того же уровня навыков).

Есть такая беда, да. Но это все равно дешевле, чем требовать от каждого разработчика навыков DB Architect.

А где, собственно, обещанные примеры случаев, когда типизация только мешает? И как же все-таки должен исполняться приведенный в качестве примера код в языках без строгой типизации?
function show_author(name) {
  if get_user(name) {
    show(name)
  } else {
    log("Something wrong with " + user)
    show(ADMIN)
  }
}

Обещанный пример: типы надо поддерживать, на это уходит время, они потому мешают.

Тесты (и документацию) надо поддерживать, на это уходит время, они потому мешают.

Код надо поддерживать, на это уходит время, он потому только мешает.

Поэтому чем меньше кода — тем лучше!

UFO just landed and posted this here
Ну и по существу статьи:
Видите тут проблему? Ну разумеется, да, мы перепутали коды ошибок. Может ли любая суперсложная система суперстрогих типов сделать это для нас? К сожалению, нет.

Когда у вас слева магические числа, а справа какие-то другие магические константы — конечно система типов ничего не сможет с этим сделать. У вас же в системе типов никак не зафиксировано, что «500» как-то относится или не относится к «INTERNAL_SERVER_ERROR». Но это можно сделать даже в простенькой системе типов а-ля тайпскрипт, что уж говорить про идрис и агду, про которые вам 0xd34df00d расскажет.

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

Когда у вас name: string, то конечно ни на что особое тут рассчитывать не приходится. А вот когда у вас name: UserName, то всё хорошо, и ничего другого вы передать просто не сможете.

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

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

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


Просто сделайте первосортную документацию для вашей кодовой базы, это совершенно не сложно.

Это очень приятное, но никак не проверяемое утверждение.

Не. Того самого внимания, которое надо уделять продумыванию и написанию кода.

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

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

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

А мне вот вспомнилась драма с Python2 -> Python3.
Ведь если бы это был язык со статической типизацией, то большая часть ошибок вылезла бы во время компиляции, а не в рантайме.
И такой огромной драмы бы не было.

UFO just landed and posted this here

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

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

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

Вы скажете: «так для динамических языков тоже есть автокомплит». Но автокомплит и другой статический анализ для динамических языков — это та же статическая типизация. Только система типов — слабая (т.е. может и сбоить), т.к. язык — не задумывался для такого использования. Это всё видно в системе javascript/typescript — где к языку, не задуманному для этого, прикрутили систему типов. И она же используется в VSCode даже для чистого javascript. Или когда JSDoc или аналоги — понимаются IDE, а это — те же аннотации типов.

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

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

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

function get_user(name) {
  db_load_user_or_die(name)
}


Здесь как раз система типов может помочь подстелить соломку. Пример на Scala:
//Класс с 4 полями, 3 из которых имеют тип String
case class User(id:Long,
                firstName: String,
                lastName: String,
                email: String)

//Функция, которая принимает то ли имя, то ли фамилию, то ли всё сразу
def findUserByName(name: String) = ???


Чтобы отличать firstName от lastName и от email введём специальные классы, которые под капотом представляют из себя обычный String:
//Вспомогательные классы
case class FirstName(firstName: String)
case class LastName(lastName: String)
case class EmailAddress(emailAddress: String)
case class FullName(fullName: String)

case class UserEnhanced(id: Long,
                        firstName: FirstName,
                        lastName: LastName,
                        email: EmailAddress)

def findUserByName(name: FullName) = ???

//Пример использования
val x: FirstName = FirstName("Mike")
val y: FullName = FullName("Mike Smith")

findUserByName(x) //Ошибка компиляции
findUserByName(y) //Нет ошибки компиляции


В Scala 3 появятся Opaque Type Aliases, которые упростят синтаксис. Это пример, когда проверка типов не даёт 100% гарантии, но однозначно помогает отлавливать логические ошибки.
Насколько я знаю, подобные алиасы уже есть кое-где.

Теперь представьте, что бизнес ситуация немного поменялась. Решением левой пятки бешеного принтера (ЛПБП), последняя пятница каждого месяца объявляется Днём Анонимности и в этот день, вместо имени, поиск должен использовать девичью фамилию пробабушки.

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

Автор спутал сильную/слабую типизацию с динамической/статической. Сколько можно?
А есть на русском что-то хорошее по этой теме? Чтобы не путать все вот эти типизации?
Я просто регулярно путаюсь, когда читаю статьи, некомфортно =)
Статьи на хабре заметно полезнее и понятнее вики, спасибо.
UFO just landed and posted this here
Когда окончательно перестанут называть вещи своими именами.
UFO just landed and posted this here

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

Я уже спрашивал вас в прошлый раз, кто ещё придерживается такой же точки зрения на типы, что и вы? Откуда вы взяли такую терминологию? Вы мне ничего не ответили, насколько я помню. Есть смысл продолжать?
UFO just landed and posted this here
Да, точно, теперь я вспомнил. Вы мне указали на книгу; я нашёл в этой книге (видимо, в более поздней редакции, чем ваша) цитату, которая опровергает вашу точку зрения. Действительно, спорить не о чем.
UFO just landed and posted this here
UFO just landed and posted this here

Вы можете поделиться источниками? А то разговор у кого редакция новее эт такое.


Сошлитесь на один публично доступный источник и давайте работать с ним.

UFO just landed and posted this here

А можно — исключительно из любопытства, еще и "the above definition", тоже в виде скриншота?

UFO just landed and posted this here

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


enum Foo {}

Отсюда же следствие что функцию fn absurd<T>(f: Foo) -> T невозможно вызвать. Ну и так далее.

UFO just landed and posted this here

под вызовом я понимаю absurd 'seq' () который возвращает не ботом.

Приятно, когда элегантные вещи — норма.

UFO just landed and posted this here

Например, чтобы потом не ловить исключения с текстом "Should never throw".

И еще раз кстати. Я же правильно понимаю, что если динамические теги используются для получения информации о коде (ака рефлексия), то это само по себе никак не влияет на то, является ли он типизированным?

UFO just landed and posted this here
Зависит от определения.

В том, которое только что процитировали, я специально в этой ветке вопрос задал.

UFO just landed and posted this here
Если, условно, всё, что вам доступно у каждого значения — получить байт с его описанием, получить его адрес и получить байт по такому-то смещению по этому адресу, то там никакой типизацией и не пахнет.

Не, это совсем не то, что мне интересно.


Мне интересно, когда мне передали, скажем, ссылку на функцию, получить число и типы ее параметров.

UFO just landed and posted this here
UFO just landed and posted this here

Честное слово, низачем. Праздное любопытство.

Показываете только одну сторону — когда типизация не помогает, игнорируя другую — когда она помогает.
function show_author(name) {
  if get_user(name) {
    show(name)
  } else {
    log("Something wrong with " + user)
    show(ADMIN)
  }
}

Кстати, не затруднит поправить этот код? Я просто не вижу, откуда взялся user — наверное, имелось в виду show_author(user) и дальше get_user(user) вместо get_user(name)? Потому что это влияет на суть примера — ошибку-то вы вроде проверяете. :) Пример не показывает, почему код бы не скомпилировался с сильными типами.

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

Я имел в виду то, что непонятно, по какой причине — как автор утверждает — не скомпилируется код с сильными типами (если автор вообще имел в виду сильные типы а не статические — я не могу додумать правильный пример за него). С сильными vs. слабыми я даже не могу придумать вариант — код выше не делает никакого преобразования типов. Если же автор имел в виду static vs. dynamic, что-то вроде name = get_user(user); if name != Null ..., подразумевая что со статическими типами мы не можем вернуть Null вместо строки и вынуждены изобретать variant type (подозреваю что это, судя по "… изобрести новый монадический тип для возможно указанного автора"), то получается уже совсем другой код, в котором get_user() сознательно написана без возможности вернуть ошибку (кодом возврата с передачей name через аргумент / пустой строкой / исключением и т.п.).

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

Похоже на то… chapuza, какие-то комментарии будут? Код в статье некорректный (а это, если что, треть всего кода в статье), хочется увидеть корректный и понять, как он иллюстрирует недостатки сильной типизации.

Вы просите меня поправить псевдокод, да еще и требуете каких-то комментариев? Ну, хорошо, пожалуйста, поправлю.


Понять я за вас не смогу, к сожалению.

Я прошу вас поправить некорректный псевдокод, в котором откуда-то магически возникают переменные (а использование других переменных бессмысленно — вы вызываете get_name() чтобы получить, внезапно, уже имеющееся name).

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

Потому что этот код с удовольствием примет любой Nihil, и правильно отработает. А с типизированным name — не скомпилируется.

function show_author(name) {
  user = get_user(name)
  if ok?(user) {
    show(user)
  } else {
    log("Something wrong with " + user)
    show(ADMIN)
  }
}

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


// name типизировано и всё-таки компилируется - поздравляю вас гражданин соврамши.
void show_author(std::string name) {  // Строка может быть пустой.
  auto user = get_user(name);  // Проверяет name.empty().

… делаете предположение, что надо изобретать собственный тип...


// Опять типизировано и опять компилируется - дважды соврамши.
void show_author(std::optional<std::string> name) {  // Из стандартной библиотеки.

… неконсистентны в том, где вы обрабатываете ошибки; user проверили перед передачей в show() но предполагаете, что show_author() может получить аргумент без проверки; ну и наконец умалчиваете о том, что в вашем варианте вам ещё потребуется написать тест, который вам покажет что ваша нетипизированная show_author() (а заодно и get_user(), потому что контроля типов и там нет) не взрывается при передаче не только строки или Null, но и какого-то другого типа.


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

хотелось бы поменьше статей

Так хотите, кто ж вам мешает.

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


Можно допустить банальную опечатку и вместо user.lastName = 'John' написать user.lstName = 'John' и в итоге у нас в объекта будет два поля, одно с опечаткой и второе без.
Статический бы язык отловил это на этапе компиляции.


Вторая проблема с полями это их переименование. При необходимости переименовать функцию или поле, при использовпнии статики ide имеет достаточно информации, чтобы заменить по всему коду. В динамике же у меня был конкретный слкчай с pycharm, где переименование автоматическое заменило только в 2х местах, а использований было 5.


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


Допустим есть два класса:


Image и Person у которых есть поле name.


Поступила бизнес задача добавить человеку firstName и lastName.
Добавляем ласт нейм и просто нейм переименовываем в firstName.
Типизированые языки позволят автоматически это сделать.


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

Использовать Guards для проверки типов?

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

Пишем больше кода и тратим время в рантайме — просто win-win какой то.

… и не забудьте написать тесты на то, что у вас есть Guard!

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

А можно пожалуйста на примере? Возьмем код товарища 0xd34df00d, на который он по моим оценкам потратил часа пол. Покажите, как потратить это время на тесты, чтобы сделать результат лучше?


revDumb : List a -> List a
revDumb [] = []
revDumb (x :: xs) = revDumb xs ++ [x]

foldlRhs : (f : b -> a -> b) ->
           (init : b) ->
           (x : a) ->
           (xs : List a) ->
           foldl f init (xs ++ [x]) = f (foldl f init xs) x
foldlRhs f init x [] = Refl
foldlRhs f init x (y :: xs) = foldlRhs f (f init y) x xs

revCorrect : (xs : List a) ->
             (f : b -> a -> b) ->
             (init : b) ->
             foldl f init (revDumb xs) = foldr (flip f) init xs
revCorrect [] f init = Refl
revCorrect (x :: xs) f init = let rec = revCorrect xs f init in
                              rewrite sym rec in foldlRhs f init x (revDumb xs)> 
UFO just landed and posted this here

Забавно...


задача: найти в массиве начало подмассива с единичками максимальной длины.

tap.eq(find_ones([1, 0, 1, 1, 0, 1, 1, 0, 1]), ?, 'дублирование максимальной длины')

Условия задачи не полны, либо она не разрешима в ряде случаев. Если бы Вы соответствующие инварианты попробовали описать формально в типах, то точно бы зацепились за этот момент.


Что там было про высокую вероятность?

UFO just landed and posted this here

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


To err is to be human, but compilers never forget.
UFO just landed and posted this here
бизнесу (читай реальному миру) как правило пофиг первое самое длинное соответствие я нашел или последнее или среднее.

Угу, а потом самолёты падают. Потому что «да как правило пофиг же».
UFO just landed and posted this here
UFO just landed and posted this here

«Let it crash» — не совсем про это :)

UFO just landed and posted this here
tap.eq(reverse([]), [], 'пустой список')
tap.eq(reverse([1,2]), [2,1], 'простой список')
tap.eq(reverse([1, 2, 3, 'a', 'b', 'c']), ['c', 'b', 'a', 3, 2, 1], 'просто случайный пример')

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


И вот [не] прохождение этих тестов в совокупности даёт нам высокую вероятность того что код [не] валиден.

Не очень-то высокую. Вот например, человек написал код (простое бинарное дерево), написал тесты, потом посадил 8 багов, и вот что получилось



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


Доклад совершенно замечательный:



и мы находим все их ошибки.

Видимо, не все. Похоже на карго культ тестирования, где тесты не тестируют ничего, а просто проходят. А когда они проходят когда они падают нужно дописать просто еще один тест)


У меня такое было на одной работе, там код тестов был наверное сложнее чем бизнес код. Существовали специальные билдеры которые создавали мок-сущности в валидном состоянии, и всякое такое. Да, это наверняка проще чем просто написать тип выражения.

Простите, конечно, а property-based testing — это не про тесты ли часом?


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

UFO just landed and posted this here
UFO just landed and posted this here
а мы тесты без моков пишем
и нам хорошо

Дадада, вы уже про это рассказывали.

UFO just landed and posted this here
UFO just landed and posted this here
вот, а это базовая абстракция на которой стоит весь веб практически

… и как вы докажете это утверждение?

UFO just landed and posted this here

А как вы из распределения популярности (это, заметим, еще даже не распределение использования) языков и фреймворков делаете вывод о "базовости" абстракции?

UFO just landed and posted this here
если большинство сайтов интернета работает на php/python/итп и их базовых фреймворках, значит большинство использует одни и те же абстракции

Слишком большое "если". На каком базовом фреймворке работает большинство (то есть строго больше 50%) сайтов интернета, и по какой статистике?

UFO just landed and posted this here
по языкам программирования согласно гитхабу лидирует Java, Python

Какое отношение это имеет к сайтам интернета?


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


по остальным популярным движкам можете пройтись сами — у всех примерно одинаково всё (в области работы с БД)

Ну вот у asp.net mvc, который тоже "популярный движок", все не так. А согласно тому же отчету доля asp.net в десять раз выше, чем питона.

UFO just landed and posted this here
а что у него не так-то?

А у него нет active record. Не принято.


или из методов этого ООП класса не сохраняют записи назад в БД?

Нет, не сохраняет.

UFO just landed and posted this here
докажите

Легко.


Вот типичный для asp.net MVC класс User:


public class User
{
  public string Username {get; set;}
  public ICollection<User> Friends {get; set;}
}

У него вообще нет ни одного метода.


при этом код надо написать на ASP.net

(Вы вообще в курсе, что asp.net — это платформа, а не язык?)


dbContext.Users
.Find(u => u.Username == "lair")
.Friends.Add(
  dbContext.Users.Find(u => u.Username == "rsync")
);
dbContext.SaveChanges();
UFO just landed and posted this here
чтож Вы задачу до конца-то не реализовали?

Как описали, так и реализовал.


где расположим этот Ваш код? внутри метода класса User, правильно я понимаю?

Нет, неправильно. Весь код класса User я привел.


В простой системе — в контроллере. В сложной — в доменном сервисе.

class FooController {
  private IDataContext  _dataContext;
  public FooController(IDataContext dataContext) {
    _dataContext = dataContext;  
  }

  [HttpPost]
  public async Task InsertRsyncToLair() {
    var lair = _dataContext
                   .Single(x=>x.Name == "@lair");
    var rsync = _dataContext
                   .Single(x=>x.Name == "@rsync");
    _dataContext.UserFriends
      .Value(p => p.One, lair)
      .Value(p => p.Other, rsync)
      .Insert();
  }
}

lair воспользовался linq2db, а то щас человек придерется к property tracking и всё по-новой. А тут всё более явно.

Это базовый антипаттерн, который 100 раз уже разругали все, кому не лень.


Но у кого-то в 2020 году это основа веб-практики, оказывается.

UFO just landed and posted this here

Плохая аналогия подобна котенку с дверцей.

UFO just landed and posted this here

Я?..


(вам назвать как минимум три альтернативы Active Record, известных никак не меньше пятнадцати лет?)

UFO just landed and posted this here

Не вижу смысла доказывать чужие утверждения.

UFO just landed and posted this here

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

UFO just landed and posted this here
давайте.

Table Data Gateway
Record Data Gateway
Data Mapper


Как бонус: Event Sourcing.

Ну это, Event Sourcing в этот список только совсем в полемическом запале можно добавить.

Зависит от от того, насколько широко понимать слово "альтернатива".


Я вот в одном проекте заменил Data Mapper на Event Sourcing.

UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here

Ну вот, например, как я решаю подобную задачу в Rust. У нас есть структура User, у которой поля приватные и отсутствует публичный конструктор. Соответственно сконструировать объект мы можем только в его модуле, где есть специальная функция, которая это делает. Функция осуществляет запрос к базе и соответственно либо конструирует объект по полученным данным, либо завершается с ошибкой. То есть, результат функции — это Result<User, DbError>. Теперь, получается, что при вызове этой функции мы обязаны запрограммировать обе возможные ветки (Result — это тип-сумма), и если где-то у нас есть структура Foo с полем типа User, я могу быть уверен, что пользователь всегда валидный, потому что получить его экземпляр возможно только если исполнение прошло по корректной ветке. Это при условии, что саму базу никто не меняет еще извне.

Такому нонейму как дядюшке бобу поверите?


The problem I have with Active Record is that it creates confusion about these two very different styles of programming. A database table is a data structure. It has exposed data and no behavior. But an Active Record appears to be an object. It has “hidden” data, and exposed behavior. I put the word “hidden” in quotes because the data is, in fact, not hidden. Almost all ActiveRecord derivatives export the database columns through accessors and mutators. Indeed, the Active Record is meant to be used like a data structure.

On the other hand, many people put business rule methods in their Active Record classes; which makes them appear to be objects. This leads to a dilemma. On which side of the line does the Active Record really fall? Is it an object? Or is it a data structure?
UFO just landed and posted this here

Вот кстати любопытно: а как может выглядеть доказательство (или доказательная методика), которому бы показывало, что AR — антипаттерн, и которому бы вы поверили?

UFO just landed and posted this here
я с Вами соглашусь: AR — антипаттерн.

Вот и прекрасно.


Жаль, конечно, что вы не ответили на мой вопрос.

UFO just landed and posted this here
UFO just landed and posted this here

А можно, пожалуйста, вопрос целиком? Потому что я прямо сейчас не понимаю, что вы спрашиваете.

UFO just landed and posted this here
и вот куда вам с типами тут притыкаться для нахождения ошибок внутри кластера операций database.save?

А, "вам с типами" — это не ко мне вопрос. Я такие задачи на языке типов решать не умею.

UFO just landed and posted this here
проблема в том что НИКТО такие задачи на языке типов решать не умеет.

… и это — повод не решать на языке типов задачи, которые на нем можно решать?


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

Я уже спрашивал, вы не ответили, спрошу еще раз: как вы из набора тестов получаете вероятность ошибки?


то продовый запрос мы возьмем и положим as is в тест.

… а оно возьмет и не упадет. Real-life-story. Потому что ему недостаточно запроса, ему еще нужно специфическое состояние БД.


(не говоря о том, что иногда могут и запрос-то не дать)

UFO just landed and posted this here

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

UFO just landed and posted this here
ну вот, соответственно Вы стоите на стороне противопоставляющей типы тестам.

Нет. Я стою на той стороне, которая считает, что от типов есть много профита.

UFO just landed and posted this here
я тоже считаю что от типов может быть профит

Я, пожалуй, просто приведу цитату: "какую пользу тут принесло указание типов? никакой".


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

Нет. Примеров выше сильно больше одного, но могу повторить, если очень хочется: типы дают коду дополнительную информацию. Тесты — нет.

UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here
я приземлённо — бизнес программирую.
всякие там деньги — товары — движение.

А мне казалось, бизнес как раз очень не любит, когда кто-то случайно доллары с евро складывает.

UFO just landed and posted this here
ага и для этого тесты есть.

Ну то есть все-таки не надо складывать что угодно с чем угодно?


Нет, скажите, вы правда считаете, что лучше обязательно писать тесты, чем иметь возможность их не писать?


доллары в евро попасть не могут

Легко.


если посмотреть на ошибки типов — то в основном строки в числа попадают

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

UFO just landed and posted this here
когда реальный мир формирует задачу, то он ее формирует так что "сложить дни выходных с днями праздников".

А это что-то сложное?


очень часто формулирует сложить что-то с чем-то отличающимся.

… и как вы будете складывать дни с вагонами?


это наверно в коде с типами. потому что тестов не пишете

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

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

Я могу только повторить про котенка с дверцей.


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

UFO just landed and posted this here
никакой.

… если проигнорировать все, что вам написали, то конечно.


кроме опечаток никаких примеров не приведено.

Ну, это просто неправда. Только я приводил примеры с подсказками в IDE и с роутингом в web api.


дополнительная информация — просто избыточное загромождение кода

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


как говаривал один известный персонаж "и это хорошо"

А что в этом хорошего?

UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here
там типы никаким боком

Это неправда. Типы там — это то, на основании чего строится проверка роута (и метаописание).


подсказки в IDE о том что в коде с типами ошибка в типах?

Нет, подсказки о том, какие операции можно сделать над конкретной переменной.


а то что язык программирования становится похож на человеческий.

А можно как-то на примере? Желательно сразу на примере того, где типы делают то же самое хуже (менее читаемо).

UFO just landed and posted this here
проверка роута строится на базе схемы свагер.

Не в приведенном мной примере кода.

UFO just landed and posted this here

А должен был?


Я не помню, чтобы я утверждал, что я такое делаю.

UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here
и мы можем выкатить релиз 31 января не парясь вопросом что впереди праздников 10 дней

Хм.


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

… то на ваши праздники это никак не повлияет?

UFO just landed and posted this here
но вероятность у "если" — очень маленькая

Какая в конкретных величинах, и как вы получили это значение?


А то понимаете, некоторые бизнесы, которые не выкатывают что-то когда-то — у них просто SLO и SLA. Соответственно, есть вероятность, есть время реакции — которое на праздниках пониже, — есть SLO. Есть из сочетания двух первых получается нарушенное третье — не выкатывают. Вот и все.

UFO just landed and posted this here

PoEAA, раздел Data Source Architectural Patterns (это десятая глава в моем издении).

UFO just landed and posted this here
многим некомфортно редактировать SQL — это аргумент на основе которого считаем антипаттерном?

Это недостаток.


Неловкий вопрос: а зачем вы цитируете определение Table Data Gateway в разговоре про Active Record?

UFO just landed and posted this here
UFO just landed and posted this here
мне дали ссылку "обоснование тут"

Вам дали ссылку на главу, где описано четыре паттерна (потому, кстати, что вы попросили "вот другое решение, которое не имеет этот перечень проблем"). И вы цитируете проблемы не от того паттерна.

UFO just landed and posted this here

Вам уже процитировали, вас это не устроило. Когда я вас спросил, что вас устроит, вы сказали — ссылка на статью с вариантами. Я вам ее дал. Теперь она вас тоже не устраивает.


Самое смешное, что вы уже признали, что AR — антипаттерн.

UFO just landed and posted this here

Во-первых, ничего некорректного в этом посыле нет. Во-вторых, это не единственный посыл.


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

UFO just landed and posted this here

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

UFO just landed and posted this here

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

UFO just landed and posted this here
>и мы находим все их ошибки.
Насколько я помню, это невозможно, потому что эта задача эквивалентна проблеме останова, и теоретически неразрешима.
UFO just landed and posted this here
Хм. А разве все решения задачи коммивояжера для больших N — не приблизительные?
UFO just landed and posted this here
Ну, если «багов приблизительно нет» вас устраивает — то да, все так же. Но в моем понимании, мы даже не можем иногда сказать, насколько приблизительно их нет.
UFO just landed and posted this here

Мне кстати интересно, как вы решаете проблему, когда все тесты начинают занимать по 5-10 часов на прогон в кластере? Я когда писал интеграционные тесты на Solidity, у меня это была основная проблема — генерация блоков блокчейне не может быть меньше секунды, соответственно каждый шаг теста тоже был с гранулярностьюв секунды. В итоге каждый тест выпонлялся 10-100 секунд, а их у меня было несколько сотен.


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


Боюсь увидеть проекты в которых сотни тысяч строк тестов. Ну, чтобы добиться схожего уровня надежности со средним жаба-проектом с 5-10к тестов.

UFO just landed and posted this here
рефакторим сами тесты.

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


Ну и давайте такой вопрос: сколько на вашем текущем проекте сейчас тестов, сколько в этом проекте строк кода, и сколько времени (примерно) выполняются все тесты.


Потому что когда я работал в той компании про которую говорил, 100 интеграционных тестов проходили полчаса. И не потому что говнаrи разработчики так написали, а потому что операции работы с БД (а там она реальная) небыстрые. Ну там, заказы поставлять, пользователей пообновлять, всякое такое.


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

UFO just landed and posted this here

Так сколько у вас тестов? Потому что по моим прикидкам, для полноценного тестирования на уровне ЯПов со статической типизацией нужно примерно 1 тест на 30-50 строк кода. Ну то есть в проекте на 50клок будет порядка 1000 тестов. Для питонов и так далее этот показатель примерно в 10 раз выше, соответственно нужен 1 тест на каждые 3-5 строчек кода.


Вот мне и интересно, насколько это

Потому что по моим прикидкам, для полноценного тестирования на уровне ЯПов со статической типизацией нужно примерно 1 тест на 30-50 строк кода.

Это что-то мало. Нужно как минимум по тесту на каждый входной параметр отдельно (на самом деле — больше).

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

Я в том смысле "мало", что мне ваша оценка кажется заниженной.

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


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

UFO just landed and posted this here

Можно поточнее? Сколько тестов в штуках на какой объем кодовой базы, и сколько занимает прогон их всех на одной машине.

UFO just landed and posted this here
UFO just landed and posted this here

Она разрешима за конечное время. Например есть довольно простой алгоритм время работы которого O((2*N) (N2)), где это возведение в степень

Хабрапарсер съел асимптотику.
Попытка номер 2: O(2^n * N^2), где ^ это возведение в степень.

UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here
О! Ещё одна «холиварная» статья!

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

Автор показал, что он относится к сторонникам первой религии. Лично я являюсь, скорее, сторонником второй. Я понимаю, что могут существовать задачи, для решения которых более удобна слабая типизация или отсутствие типизации как таковой. Да, практически на каждое утверждение автора я мог бы привести контрпример или написать опровержение, но зачем? С одной стороны, из комментариев видно, что любая аргументация/контраргументация тут только провоцирует дальнейшие споры. У автора есть своя точка зрения, и он её отстаивает. С другой стороны, начиная писать контраргументы, я вроде как, начинаю пропагандировать свою «религию».

Строгая типизация, по-моему, является системой ограничений, которые позволяют в итоге существенно упростить _мне_ жизнь. Но они при этом остаются ограничениями, либо не достаточно сильно, либо вообще не упрощающими жизнь, с точки зрения сторонников другой «конфессии». Так что какой бы аргумент я не привёл, всегда можно сказать, что он недостаточно хорош, что цена слишком высока, что он вообще не верен,… etc.

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

P.S. chapuza скажите, как вы выбираете, о чём будете писать? Что вами двигало, по крайней мере, при написании двух последних статей? Предыдущая ваша статья вызвала большой резонанс. Эта тоже, похоже, будет резонансной. Обе статьи, с моей точки зрения, являются провокационными. Но вашу карму я не минусовал, а даже вовсе наоборот. Статью тоже не минусую.
Автор показал, что он относится к сторонникам первой религии.

Нет.


У автора есть своя точка зрения, и он её отстаивает.

Нет.


Что вами двигало, по крайней мере, при написании двух последних статей?

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


У меня излишне критический склад ума. Когда я вижу хайп, я сразу начинаю придумывать контраргументы — в большинстве случаев, просто ради того, чтобы осознать: ага, хайп не на пустом месте, эти люди правы потому, что, похоже, они действительно правы. Вместо authority bias, вместо слепого следования за толпой, я стараюсь разобраться в проблемах самостоятельно.


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


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

Автор показал, что он относится к сторонникам первой религии.
Нет.
У автора есть своя точка зрения, и он её отстаивает.
Нет.
Интригующе. Но не суть. Простите, что неправильно вас понял.
Что вами двигало, по крайней мере, при написании двух последних статей?
Желание посеять крупицу сомнений в каждом, кто по недомыслию примкнул к колонне марширующих строем. Много мнений — лучше одного, по крайней мере потому, что тренирует мозг необходимостью осознанного выбора.

Что-то подобное я и надеялся услышать. Спасибо. У вас получается «посеять крупицу сомнений в каждом» ;) Поэтому, собственно, и закинул плюс в карму после предыдущей статьи.

Ещё мне нравится ваш подход, никого не минусовать. Стараюсь тоже ему следовать.

Успехов вам, и жду следующих неоднозначных статей!

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

Каждый раз в подобный статьях одно и то же: обсуждаются преимущества концепций программирования (ООП, функциональной, SOLID, TDD, сильной или слабой типизации и т.д.), на простейших примерах веб-разработки, кода по сути надо достать что-то из базы отправить ответ на запрос. По-моему, в подобных случаях любой выбор концепций сойдет, если ими правильно пользоваться. Потому что концепции программирования — они прежде всего про борьбу с непостижимой для человека сложностью а не про борьбу с человеческой недисциплинированностью или за комфорт.

Представим, что все намного сложнее. Допустим UI — это нечто вроде графического редактора, а на сервере есть много сложного кода, который держит данные в памяти, анализирует, кэширует результаты расчета, использует сложные алгоритмы, вычисляет нечто с использованием сложных алгоритмов, в том числе эвристических, и все это должно быстро работать. Например, разрабатывается САПР или IDE для программирования, или что-то вроде Google docs.
Начинаем создавать эту систему, придумываем концепции, уровни абстракции, которые позволяют не утонуть в сложности. В какой-то момент все становится слишком сложно и непонятно никому. Тогда надо в каком-то месте системы изобрести абстракции получше и рефакторить соответствующие модули.
Какой код в этот момент лучше иметь? С сильной типизацией или без?
В типизированном коде классы уже отражают часть заложенных в основу концепций и абстракций. В не типизированном эти концепции не имеют столь реального воплощения и находятся в голове программиста, прочитавшего комментарии.
Во-первых, насколько легко половину работы по рефакторингу выполнять у себя в голове? Новая концепция будет выражена в комментариях, но они могут быть неоднозначны т.к. человеческий язык не настолько формален. Дальше, концепция будет выражена в вызове функций, но если классы и типы не указаны, то гораздо меньшая часть концепции оказывается выраженной в коде. Аналогия — насколько легко создать теорию не имея ручки и бумаги для записи промежуточных выкладок и формул? Где гарантия, что вашу теорию все кто с ней ознакомится будет понимать одинаково в отсутствии промежуточных выкладок?
Во-вторых, как бы хороши не была IDE, для языка без сильной типизации она не гарантирует что все вызовы функций, которые мы поменяли будут легко находиться в процессе рефакторинга. IDE не сможет ничего подсказать или заподозрить ошибку в виде warning. Корректность вызовов функций придется проверять самому или тестами.
Конечно классы и сигнатуры вызовов не гарантируют, что все будет правильно сразу как только все скомпилировалось, но это инструмент, который во-первых позволяет отразить больше идей в коде и во вторых переложить на компьютер формальную часть работы по проверке корректности.
В результате с сильной типизацией предельный уровень сложности, до которого можно развивать продукт существенно повышается.
это инструмент, который во-первых позволяет отразить больше идей в коде и во вторых переложить на компьютер формальную часть работы по проверке корректности

Согласен безоговорочно (ну, часть формальной работы, да, не всю).


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

В принципе, согласен. Просто систему, спроектированная изначально модульно, с четкими boundaries (минимум кросс-зависимостей и кросс-вызовов) — еееще проще рефакторить. Без типов. Иногда даже без тестов.

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

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

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

У-у-у-у, как все запущено :)


Если вкратце, тут уже плохо буквально все.


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

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

вводить классы библиотека (модуль) не должна. Она должна вводить интерфейсы.
Пускай будут интерфейсы + IOS контейнер. ИМХО, когда вся сложность продукта сосредоточена в его алгоритмах это полезно, но не меняет картину кардинально.
улучшая модуль, не нужно ломать интерфейс (можно расширить)
Допустим, теперь используется другой метод расчета (например, более эффективный, но более требовательный к условиям использования) и другая система абстракций для построения этой части приложения (возможно даже другой уровень абстракций). Все стало по-другому.
Исключительно расширениями можно обойтись в относительно простых случаях, когда по сути речь идет только об извлечении/изменении каких-то данных, быстродействие не критично а продукт пишется в один заход. В таких программах сложность еще не является критической проблемой, представляющей реальную угрозу дальнейшему развитию продукта. В сложных программах, которые развиваются годами глобальные переработки неизбежны. Кроме того, переработку придется выполнять и внутри модуля, который не особо поддавался в свою очередь разбиению на еще более мелкие модули.
модуль должен полностью инкапсулировать внутреннее состояние.
Конечно должен. И часто он так и делает.
модуль должен быть максимально изолирован от внешнего мира и минимален по функциональности.
Конечно должен. Например, когда его разрабатывали, делали его для решения частной задачи и еще не знали что его захотят использовать в 5-и других под-проектах в рамках этого продукта совершенно неожиданным образом. Сразу они говорили, что делают это временно, чтобы быстро выкатить функционал. Потом правда это оказалось постоянным. Когда это выяснилось, решили что нужен другой подход. Например, теперь это будет несколько других специализированных модулей, решающих те же задачи несколько по другому. И так далее…
Я к тому, что настоящие преимущества технологий как раз проявляются не в стерильных условиях а в самых сложных и масштабных случаях.
Образно говоря, программисты всегда обсуждают преимущества технологий строительства на примере эффективного возведения 2-этажных коттеджей, вместо того чтобы рассмотреть задачу возведения небоскреба. Потому что последний без технологий вообще не построишь, а коттедж с большими или меньшими затратами так или иначе можно возвести. При этом небоскребы строятся повсеместно и не являются каким-то уж прямо исключением.
Она должна вводить интерфейсы.

А как они должны описываться?

Выразительными средствами языка. (Каков вопрос — таков ответ.)


В C, например — это заголовочные файлы.

Как использовать эти ВC?
Допустим, некая функция анализирует связанные в граф доменные объекты и возвращает некий результат. На вход функции подается стартовый объект, функции-предикаты определяющие какие объекты можно проходить в графе, какие нужно искать, какие-то настройки расчета, некоторые из которых необязательные.
На выходе — структура данных с результатами анализа (удался, был прерван, нашел нечто искомое, перечень пройденных объектов и т.д.).
Как без сильной типизации решаются вопросы:
— как понять тип каждого входного параметра; например, это просто флаг boolean или предикат, если предикат, то что он ждет на входе и для какого класса доменных объектов годится, а о каких он пока знать не знает и работать с ними не может?
— как понять, какие именно атрибуты могут присутствовать в возвращаемых результатах? Какой из них определен всегда, а какой может быть null?
— допустим, кто-то решил, что этот набор предикатов и флагов был задуман неудачно, под слишком частные случаи и скорректировал его, чтобы сделать более универсальным и пригодным для большего количества задач; как теперь найти и привести в соответствие все модули, которые эту функцию использовали? Ошибка при компиляции или warning depricated не появится. Искать по коду всех модулей, созданных разными разработчиками для разных целей, запускать тесты и потом каждому писать таск, чтобы проверили не поломалось ли чего? Если разработчик считает что не поломалось и тесты прошли, то значит все хорошо, или просто разработчик просто упустил суть изменений, а тест был для более простого случая?
При сильной типизации новая сигнатура метода будет или частично совместима со старой или возможно совсем нет. Таким образом компилятор возьмет на себя сразу часть фукнций по организации всех работ.
Более того, уже на этапе разработки программист может увидеть что часть потребностей чужих модулей несовместима с его классной доработкой и откатить все назад никого не беспокоя. Сумеет ли он так же легко разобраться в этом при слабой типизации?

Не является ли это разновидностью статической типизации?

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

Потому что никто не будет читать статью с примерами кода на тысячи строк кода.


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

Спасибо за ссылку, утащил в букмарки

UFO just landed and posted this here

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

только компилятор и IDE не смогут ей воспользоваться для помощи программисту

Как вам там, в 1998 году, с Notepad32 живется?

К какая IDE умеет извлекать информацию о типах из документации? Возможно я чего-то не знаю, но пока я вижу некоторые усилия по формализации описаний типов (type annotations в питоне, typescript) а не разбор естественного языка в документации.

Любая, которая умеет в Language Server Protocol от Microsoft, для всех языков, в которых предусмотрена информация о типах в документации.

Ну не знаю, у меня вот VS Code с питоном (выполнены оба условия?) регулярно ничего не показывает. С аннотациями типов не в документации — показывает.

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


Хорошо, значит не любая, Переформулирую четче.


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

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


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

Забавный факт: изначально фраза «strongly hyped» была про C++, который не является строго типизированным языком, в отличие от Java и С# например.
И речь в статье все таки про статическую типизацию против динамической, а не про строгую против слабой.

Далее — первый пример из статьи не имеет отношения к типизации вообще.
А вот второй пример как раз таки именно тот который типизацией можно решить.
Есть такой антипаттерн — primitive obsession. У него две формы, первая — когда вместо того, чтобы создать полноценную модель делают что то вот такое List<Dictionary<string, Dictionary<string, List<…
А вторая форма, когда для идентификаторов используют примитивы. Бороться с этим просто — завернуть идентификатор в объект или структуру.
например вот так
export interface CompanyId {
  companyId: number;
}

export async function getCompany(id: CompanyId): Promise<Company> {
}

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

типизация не серебряная пуля, и тестирование собой не заменит. Это, как и тесты, еще один фильтр на пути грязи в код. Многоуровневая система фильтрации работает лучше чем одноуровневая.
И еще одно — на Javascript люди привыкли писать лютую дичь. Приходят в проект на typescript и пишут тоже самое, подставляя «any». Я же прошу написать полноценную типизацию той клоаки которую они родили. Как результат — люди начинают писать более удобоваримый код, качество их кода растет, просто от введения типизации.
никто до сих пор не заметил

Там уже были попытки. Динамическая типизация — это оксюморон, я (как и примерно все в англоязычном современном интернете) — использую слово «Type» в значении «Тот тип из теории типов». Поэтому уточнение «Strong» там и имеет смысл.


первый пример из статьи не имеет отношения к типизации вообще

Если вы не видите суслика, это не значит, что его нет.


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

Пруфлинк, или не считается.


типизация не серебряная пуля

Да, об этом и заметка, собственно. Но если вы почитаете комментарии, вы узнаете, что многие с этим не согласны.

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

опять же, отнюдь. Типизация призвана ловить ошибки несоответствия типов. логическую ошибку типизация ловить не призвана. Пример именно на логическую ошибку. И тесты тут кстати тоже не помогут. Потому что ту же логическую ошибку автор такого кода совершит и в тесте тоже. Опять же было бы проще «перепутать» 401 и 403, их путают постоянно, сколько раз уже встречал. Люди также не знают в чем разница между авторизацией и аутентификацией, как вот тут между строгостью и статичностью…
UFO just landed and posted this here
а если осмотреть найденные ошибки несоответствия то выяснится что в ТОП 1 (95%) этих ошибок будет — строка пришедшая вместо чиселки

… так я еще раз спрошу: где посмотреть на статистику?


и вот если осмотреть ТОП1, то окажется что это просто операторы языка сдизайнены некорректно.

И какой же корректный дизайн операторов языка поможет вам от ошибки "не могу совершить арифметическую операцию над строкой"?

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

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

UFO just landed and posted this here
В комментарии, на который сослался ваш оппонент, прямая цитата из RTFM'а, с которой вы как раз и спорите.
ну то, что вы с ним курите какие то альтернативные источники это понятно.
Простые смертные за источник считают вот это
Динамическая типизация

если не нравится википедия, то могу направить в школу
цитата — JavaScript has dynamic types.
не «нетипизованный» язык, а динамически типизованный.
Там же можно почитать какие типы бывают в языке. Что то я там типа «any» не нашел, а вот строковый, числовой и булевый типы, внезапно, есть.
UFO just landed and posted this here
Ссылку то можно, что за источник?
UFO just landed and posted this here
И, внезапно, тут написано ровно тоже самое. Системы типов бывают нетипизованные (ассемблер, брейнфак, там правда они не приведены), и типизованные.
Типизованные делятся на матрицу статические/динамические по горизонтали и строгие/слабые по вертикали. JS и питона там нет, видимо тогда еще не было, зато есть Perl и Java. Страница 14

UFO just landed and posted this here
Я вот вижу комменты, что динамических типов не бывает вообще, как будто это нетипизованный язык, претензий к немного неточному переводу вот только сейчас вижу.

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

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

Да, автор вообще дебил, это вы прямо в точку.

О, я долго думал, что статья скрыта или удалено, но нет — вот она, со всеми комментариями. Круто! Теперь вопрос — что же это было и зачем chapuza

Есть некоторые вещи, про которые суждено лишь догадываться.

О, я долго думал, что статья скрыта или удалено

А это ровно так и было. Какое-то время эта статья была убрана в черновики.

Товарищ майор, у вас ус отклеился.


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

Вы не можете с точностью знать, почему статья была недоступна.

Зато могу предполагать (с весьма большой долей уверенности), что если при переходе по ссылке на статью показывается "эта статья была скрыта в черновики", то она… убрана в черновики.

Articles