Есть одна ловушка читаемости кода, которой легко избежать, если вы о ней знаете; тем не менее она встречается постоянно: это отсутствующие единицы измерения. Рассмотрим три фрагмента кода на Python, Java и Haskell:

time.sleep(300)

Thread.sleep(300)

threadDelay 300

Сколько «спят» эти программы? Программа на Python выполняет задержку на пять минут, программа на Java — на 0,3 секунды, а программа на Haskell — на 0,3 миллисекунды.

Как это можно понять из кода? А никак. Вам просто нужно знать, что аргументом time.sleep являются секунды, а threadDelay — микросекунды. Если вы часто ищете эту информацию, то рано или поздно её запомните, но как сохранить читаемость кода для людей, никогда не встречавшихся с time.sleep?

Вариант 1: вставить единицу измерения в имя


Вместо этого:

def frobnicate(timeout: int) -> None:
    ...

frobnicate(300)

сделаем вот так:

def frobnicate(*, timeout_seconds: int) -> None:
    # The * forces the caller to use named arguments
    # for all arguments after the *.
    ...

frobnicate(timeout_seconds=300)

В первом случае мы даже не можем сказать в месте вызова, что 300 — это таймаут, но даже если бы мы это знали, то 300 чего? Миллисекунд? Секунд? Марсианских дней? И напротив, второй пример совершенно не требует объяснений.

Использование именованных аргументов — удобная возможность для языков, которые её поддерживают, но это не всегда возможно. Даже в Python, где time.sleep определяется с одним аргументом по имени secs, мы не можем вызвать sleep(secs=300) из-за особенностей реализации. В таком случае можно присвоить имя значению.

Вместо этого:

time.sleep(300)

сделаем так:

sleep_seconds = 300
time.sleep(sleep_seconds)

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

Вариант 2: использовать строгие типы


Вместо вставки единиц измерения в имя можно использовать более строгие типы, чем integer или float. Например, мы можем использовать тип duration.

Вместо этого:

def frobnicate(timeout: int) -> None:
    ...

frobnicate(300)

Сделаем вот так:

def frobnicate(timeout: timedelta) -> None:
    ...

timeout = timedelta(seconds=300)
frobnicate(timeout)

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

Область применимости


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

Например, возвращайте не такое:

{
   "error_code": "E429",
   "error_message": "Rate limit exceeded",
   "retry_after": 100,
}

а такое:

{
   "error_code": "E429",
   "error_message": "Rate limit exceeded",
   "retry_after_seconds": 100,
}

Не создавайте таких файлов конфигураций:

request_timeout = 10

лучше выберите один из этих вариантов:

request_timeout = 10s
request_timeout_seconds = 10

И не проектируйте бухгалтерское CLI-приложение таким образом:

show-transactions --minimum-amount 32

выберите один из этих вариантов:

show-transactions --minimum-amount-eur 32
show-transactions --minimum-amount "32 EUR"

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


  1. Keeper1
    31.05.2022 16:24
    -11

    Всё уже придумано до нас: https://ru.wikipedia.org/wiki/Венгерская_нотация


    1. fougasse
      31.05.2022 16:51
      +14

      И как она поможет? В контексте единиц времени.

      m — это минута или месяц? Зачем запоминать совсем неочевидные сокращения? Экономить место, чернила в принтере листингов? Сколько их будет, сокращений?

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


      1. Keeper1
        31.05.2022 17:36

        Никто не мешает придумать свои префиксы и их невозбранно использовать. Например, min, sec и так далее. Аналогично для длины, веса и прочего.


        1. gecube
          31.05.2022 22:27
          +6

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


          1. Keeper1
            01.06.2022 08:23
            -1

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

            P.S. Господи, ещё один адепт секты всеобщей типизации. Откуда вы только лезете в таких количествах?


            1. 0xd34df00d
              01.06.2022 08:27
              +6

              Из основ теоретической информатики и матлогики А что вам не нравится в типах?


              1. Keeper1
                01.06.2022 09:35

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

                Мне не нравятся фанатики. «Только ситхи всё возводят в абсолют».


                1. 0xd34df00d
                  01.06.2022 18:23

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

                  Большинство из реализаций не основывалось на достаточно формальных принципах, увы.


                  Мне не нравятся фанатики. «Только ситхи всё возводят в абсолют».

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


    1. dom1n1k
      31.05.2022 18:41
      -1

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


      1. Keeper1
        31.05.2022 18:55
        -2

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


        1. fougasse
          31.05.2022 20:13
          +2

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


          1. Keeper1
            31.05.2022 21:38

            Я даже не пытаюсь.


      1. gro
        31.05.2022 20:19
        +4

        Другой автор в красках описывал, как ВН может быть полезна, если её правильно готовить: https://www.joelonsoftware.com/2005/05/11/making-wrong-code-look-wrong/
        Но не понимаю, как это можно применить к единицам измерения.


        1. ivegner
          31.05.2022 22:34
          +2

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


        1. AnthonyMikh
          01.06.2022 20:03

          Другой автор в красках описывал, как ВН может быть полезна, если её правильно готовить:

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


          1. gecube
            02.06.2022 01:28
            +1

            полностью поддерживаю

            @gro там хороший пример, но надо понимать, что венгерская нотация в духе "Systems Hungarian" - это бред. Семантическая (которая "Apps Hungarian") - ее вообще язык венгерской нотацией не поворачивается назвать... А вполне себе просто нормальный способ именования переменных, но все равно он не поможет в случае, когда у Вас есть контейнер для времени, и куча возможностей засунуть туда разные единицы измерения. Примерно как в случае@ivegner Чем мне поможет наличие разных названий у переменных, если компилятор мне по рукам не даст при их приведении друг к другу? К сожалению, это может сделать только система типов.

            @Keeper1 а почему префиксы должны быть одни, а не другие? Или скажем, у Вас будет префикс mGeorg и mKeeper - и как определить - какой из них для месяца (month), а какой для минуты? Или почему не делать это на другом языке, скажем, венгерском - perc для минуты? Или еще как-то? Я понимаю в системе типов, там хотя бы жестко ограничиваешь возможности перехода туда-сюда...


            1. Keeper1
              02.06.2022 11:27

              Цитирую себя же:

              Никто не мешает придумать свои префиксы и их невозбранно использовать. Например, min, sec и так далее. Аналогично для длины, веса и прочего.

              Аналогично для венгерского языка. Главное, чтобы 1С не получился.


              1. gecube
                02.06.2022 13:58

                не нужно. Почему - я объяснил. Лучше вообще типизированный объект Time(minutes=60, seconds=10)....


  1. crea7or
    31.05.2022 16:54
    +12

    надо же, плюсы не очень-то и отстают...

    std::this_thread::wait_for(100ms);


    1. artemisia_borealis
      31.05.2022 23:15
      +2

      Хоть и не так давно завезли, но это очень полезно…

      А (La)TeX так вообще лет сорок уже поддеживает

      \hspace{1.5cm}
      \addtolength{\hoffset}{-0.5mm}
      \addtolength{\textwidth}{1sp}
      \vspace{12pt}
      \rule{3mm}{.1pt}
      


    1. slovak
      01.06.2022 11:57
      +1

  1. DmitryMurinov
    31.05.2022 17:20
    +12

    Телепортируемся в Java из редакции языка (и распространённых примеров) 1990-х в немного более свежую и пишем:

    TimeUnit.MILLISECONDS.sleep(300); TimeUnit.MINUTES.sleep(5) и т.д..


    1. WraithOW
      31.05.2022 18:47
      +4

      Котлин и его экспериментальный time еще приятней
      delay(300.milliseconds)


      1. mihmig
        31.05.2022 22:04
        +2

        К чему эти ненужные скобки и точка? Они несут лишнюю когнитивную нагрузку.
        Давайте придумаем новый язык, например Мотлин, в котором можно будет писать так:
        delay 300 milliseconds
        (Ведь код чаще читают, чем компилируют и пишут!)
        И да, в новом языке "забудем" про статические методы, накостылив их через companion object!


        1. 0xd34df00d
          31.05.2022 22:13
          +7

          К чему эти ненужные скобки и точка?

          Хаскелисты услышали вас. Там скобки в таких случаях не пишутся.


          Статических методов там, к слову, тоже нет (за неимением объектов).


        1. impwx
          01.06.2022 12:35
          +7

          Как раз наоборот — до определенного предела знаки препинания позволяют быстрее понять, что именно за магия тут накручена. Например, если я вижу delay(300.milliseconds), я сразу могу определить, что:


          1. Вызывается метод delay с одним аргументом
          2. Есть свойство milliseconds, скорее всего расширяемое, объявленное на типе целых чисел
          3. Тип возвращаемого значения этого свойства совпадает с типом аргумента метода delay

          Если же я вижу delay 300 milliseconds, то можно интерпретировать эту конструкцию совершенно по-разному:


          1. У метода delay два аргумента, первый — число, второй — некий алгебраический тип, перечисление единиц измерения времени, конструктором которого является milliseconds
          2. У метода delay один аргумент, а milliseconds — это постфиксный оператор (?), преобразующий число в специальный тип с диапазоном
          3. У метода delay больше двух аргументов, и на этой строке мы только частично применили функцию, не вызвав ее — т.е. задержки не будет

          Так что читаемость — это штука сугубо субъективная, и с количеством знаков препинания оно напрямую не коррелирует.


  1. leremin
    31.05.2022 17:27
    +5

    В C# TimeSpan стараюсь использовать. Но вообще, по крайней мере в моей сфере, проблема глубже. Например, rotate(double angle) - градусы или радианы?


    1. onyxmaster
      31.05.2022 17:46
      +2

      Можно использовать доменные типы, у которых единица измерения указана в имени типа, например rotate(Degrees angle). Вообще борьба с primitive obsession очень полезная штука (правда не во всех языках легко даётся).


    1. SadOcean
      31.05.2022 18:49
      -1

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


      1. andreishe
        31.05.2022 19:11
        +4

        Если были бы градусы - было бы подписано "degrees". Радианы - единица измерения углов по умолчанию во многих "небытовых" контекстах.


        1. Busla
          31.05.2022 19:35
          +9

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


    1. Deosis
      01.06.2022 07:10
      +2

      В f# пошли ещё дальше в ввели единицы измерения.

      https://docs.microsoft.com/en-us/dotnet/fsharp/language-reference/units-of-measure

      Таким образом можно сделать так:

      [<Measure>] type rad
      let rotate (angle : double<rad>) = ...

      И уже не получится случайно передать в функцию градусы.


  1. DirectoriX
    31.05.2022 18:07
    +9

    У использования специальных типов есть ещё одно преимущество перед абстрактными числами: если сложить 5 минут и 30 секунд — получится точно 5:30, а не 35… чего-то.


    1. artemisia_borealis
      31.05.2022 23:18
      -2

      Однако, появляются сложности с решением нефизических задач типа «найдите сторону квадрата у которого площадь равна периметру» и.т.п. :)


      1. DirectoriX
        31.05.2022 23:33
        +8

        Обычно спец. типы поддерживают преобразование обратно, например время в Rust

        Duration::from_millis(123).as_secs_f32(); //обычное число

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


  1. SergeiMinaev
    31.05.2022 18:24
    +10

    sleep_seconds = 300

    time.sleep(sleep_seconds)

    Зачем писать два раза sleep? Вполне хватитsleep(secs) . Излишняя подробность тоже утомляет.

    Даже в Python, где time.sleep определяется с одним аргументом по имени secs, мы не можем вызвать sleep(secs=300) из-за особенностей реализации.

    Можем: sleep(secs:=300).


    1. baldr
      03.06.2022 13:07
      -1

      Так-то можно и sleep(kilogram:=300)


  1. SadOcean
    31.05.2022 18:50
    +13

    О да, вспоминаю прекрасную функцию в нашем проекте, которая имела 2 оверлоада
    - int в мс
    - float в сек
    Timer.Show(1, () => {});
    и
    Timer.Show(1f, () => {});
    Различались по работе всего в 1000 раз.


    1. fougasse
      31.05.2022 20:14
      -4

      Как это прошло код-ревью?


      1. slonopotamus
        31.05.2022 20:31
        -1

        А даже если как-то и прошло, почему не было выправлено тут же как только проблема была обнаружена?


        1. SadOcean
          01.06.2022 11:21
          +1

          Когда была обнаружена - оно и было исправлено.


      1. SadOcean
        01.06.2022 11:20
        +2

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


    1. PowerMetall
      01.06.2022 15:48

      Timer.Show(1, () => {});
      и
      Timer.Show(1f, () => {});

      Особенно весело наверное выглядело бы в коде, например

      var timeToShow = 100; // да да, название переменной не соответствует теме статьи ))
      ...
      // куча
      // некого
      // кода
      ...
      Timer.Show(t, () => {}); 


  1. tormozedison
    31.05.2022 18:58
    -1

    С одной стороны — «идеи витают в воздухе», с другой — «ну почему я сам до этого не додумался».


  1. agalakhov
    31.05.2022 19:10
    +3

    Используйте типы.


    sleep(Seconds(10));
    sleep(10.ms()); // в Rust можно так


    1. includedlibrary
      31.05.2022 19:17
      +5

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


    1. igrishaev
      31.05.2022 20:28

      А как потом воевать с классами `com.google.Seconds`, `com.apple.util.Seconds`, `com.netflix.time.Seconds`, `org.someshit.Seconds`?


      1. cepera_ang
        31.05.2022 21:52
        -1

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


        1. 0xd34df00d
          31.05.2022 21:56

          Что значит «ведут себя как секунды»?


          1. cepera_ang
            31.05.2022 22:05
            +3

            Передаются в функции ждущие секунды, конвертируются в минуты, тикают :)


            1. 0xd34df00d
              31.05.2022 22:14
              +1

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


              1. Cerberuser
                01.06.2022 06:27

                Не тип, а type class, и, возможно, не в языке, а в широко распространённой библиотеке. Пример из реальной жизни - сериализация: serde предоставляет тайпклассы (трейты, в терминологии Rust) Serialize и Serializer, тип данных реализовывает первый, формат сериализации (json, bincode, etc.) - второй, и serde обеспечивает их склейку. Так же, технически, может быть и тут - тайпкласс для секунд, который может быть реализован кем угодно, хоть гуглом, хоть эпплом, хоть нами самими (вероятнее, правда, это всё-таки будет тайпкласс Duration, без явной привязки к конкретной единице измерения, но с возможностью конвертироваться в них).

                В случае конкретно Rust, впрочем, это не нужно, именно потому что есть https://doc.rust-lang.org/stable/core/time/struct.Duration.html (не в языке, формально, но в стандартной библиотеке), так что воевать особо ни с чем не нужно. Ну, разве что учитывать, что стандартный тип всё-таки ограничен и иногда лучше использовать "стандарт де-факто" в виде https://docs.rs/chrono/latest/chrono/struct.Duration.html (но опять-таки, это именно "стандарт де-факто", а не "один из многих").


                1. 0xd34df00d
                  01.06.2022 08:00
                  +1

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


                  вероятнее, правда, это всё-таки будет тайпкласс Duration, без явной привязки к конкретной единице измерения, но с возможностью конвертироваться в них


                  1. Cerberuser
                    01.06.2022 08:06

                    Я имею в виду, что функция, принимающая impl Duration, сама может дёрнуть на нём seconds(), milliseconds() или что там ей нужно, чтобы получить сырое число для передачи в низкоуровневый метод (системный вызов или FFI). Либо продолжит работать с impl Duration как с чёрным ящиком, который можно прибавлять к impl Instant для получения другого impl Instant и сравнения, например, с Instant::now.


                    1. 0xd34df00d
                      01.06.2022 08:28
                      +1

                      Я имею в виду, что функция, принимающая impl Duration, сама может дёрнуть на нём seconds(), milliseconds() или что там ей нужно, чтобы получить сырое число для передачи в низкоуровневый метод (системный вызов или FFI).

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


                      Либо продолжит работать с impl Duration как с чёрным ящиком, который можно прибавлять к impl Instant для получения другого impl Instant и сравнения, например, с Instant::now.

                      Спать рано или поздно придётся в конкретных единицах времени, увы.


                      1. Cerberuser
                        01.06.2022 08:43

                        это ведь как раз подмножество типов, связанных со временем, но в ядре языка.

                        Не совсем понимаю, что за "подмножество". Есть один тайпкласс, который предоставляет несколько возможностей получить условный Integer, в зависимости от того, что нужно нижележащему слою. О каких типах тут речь?

                        Спать рано или поздно придётся в конкретных единицах времени, увы.

                        А как, по Вашему опыту, может быть реализовано это "спать"? Я вижу два варианта: либо упомянутый syscall/FFI (которым, с точки зрения типов, нужен именно "условный Integer"), либо "периодически просыпаться, проверять, не пора ли продолжать, если не пора - спать дальше", который прекрасно реализуется по принципу "запомнили impl Instant в начале сна и impl Duration на его продолжительность, при пробуждении проверяем, что start + duration < now".


                      1. 0xd34df00d
                        01.06.2022 08:52

                        О каких типах тут речь?

                        В данном случае — о самом этот тайпклассе.


                        А как, по Вашему опыту, может быть реализовано это "спать"? Я вижу два варианта [...]

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


                      1. Cerberuser
                        01.06.2022 09:01

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


          1. SadOcean
            01.06.2022 12:50

            Значит "реализуют интерфейс, характерный для секунд"
            Трейты можно реализовывать вне объявления типа, поэтому можно дописывать типы под нужды библиотек.
            Соответственно через трейты можно теоретически объеденить несколько библиотек, считающих время, просто при вызовах типы будут прозрачно конвертироваться или реализовывать нужные интерфейсы поверх.


        1. igrishaev
          01.06.2022 09:44
          +1

          Отлично. А чей трейт использовать -- com.google.TraitSeconds, com.apple.TraitSeconds, org.someshit.TraitSeconds?


          1. Cerberuser
            01.06.2022 10:50
            -1

            rs.time.TraitSeconds, который будут реализовывать все, кто хочет, чтобы их использовали не только в рамках их собственной узкой ниши - и google, и apple, и someshit.


            1. igrishaev
              01.06.2022 10:53
              +2

              Значит, для начала надо затащить rs.time.TraitSeconds в стандартную библиотеку. А потом еще градусы цельсия, килограммы, радианы, ньютоны, амперы, года, световые года и прочее. Кто этим будет заниматься?


              1. Cerberuser
                01.06.2022 10:55

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


              1. AnthonyMikh
                01.06.2022 20:09

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


  1. Savevur
    31.05.2022 19:13
    -1

    это таймаут, но даже если бы мы это знали, то 300 чего? Миллисекунд? Секунд? Марсианских дней?

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

    https://ru.wikipedia.org/wiki/Международная_система_единиц


    1. SergeiMinaev
      31.05.2022 19:58
      +1

      Если ничего не указано, то значит

      Не всегда. Timestamp в JS - миллисекунды, в Python - секунды.


      1. Savevur
        31.05.2022 20:36

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

        Немного непонятно я написал. Ок. Если я получил от вас код, и у вашей переменной, которой вы сами придумали имя, нет постфикса единицы измерения, то я предполагаю, что в переменной вы храните величину в единицах СИ.


    1. fougasse
      31.05.2022 20:18
      +3

      А потом вы оказываетесь в США с футами, милями, и унциями. И ваш условный

      double getEnergyContent(double weight);

      летит в тар-тарары


      1. Savevur
        31.05.2022 20:31

        Это не мой, а ваш условный. Если практики ни в чем нет, в том числе в интерфейсах международных программ, то можно улететь и от более простых вещей. Всё внутри движка делать в единицах СИ, и ни к чему там префиксы-постфиксы.


    1. igrishaev
      01.06.2022 10:54

      А если код написан на Земле, но выполняется на Луне, то что?


  1. falconandy
    31.05.2022 19:20
    +3

    В Go используется тип time.Duration:

    time.Sleep(time.Second * 30)
    time.Sleep(time.Millisecond * 300)
    
    time.Sleep(300) // nanoseconds
    


    1. Savevur
      31.05.2022 19:34
      +1

      1. konst90
        31.05.2022 20:44
        +1

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


    1. slonopotamus
      01.06.2022 17:08
      +2

      time.Duration - это недоразумение, а не типизация.

      Вот это вот компилируется:

      time.Sleep(time.Second * time.Second)


      1. falconandy
        01.06.2022 17:29

        Внутри Duration это всего лишь int64:

        // A Duration represents the elapsed time between two instants
        // as an int64 nanosecond count. The representation limits the
        // largest representable duration to approximately 290 years.
        type Duration int64
        

        Ваш пример конечно компилируется, но компилятор можно/нужно дополнять линтерами. Например, golangci-lint покажет сообщение от линтера durationcheck:
        Multiplication of durations: `time.Second * time.Second` (durationcheck)
        time.Sleep(time.Second * time.Second)
        


        1. slonopotamus
          01.06.2022 19:15
          +2

          Внутри Duration это всего лишь int64

          То что есть zero-cost абстракции - это совершенно нормально. Ненормально что эта конкретная абстракция дырява чуть менее чем полностью.

          Вот так кстати тоже можно:
          time.Sleep(time.Second + 42)

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


          1. falconandy
            01.06.2022 20:23
            -1

            В данном случае 42 — это не апельсины, а 42 наносекунды (те же яблоки). Вышеупомянутый линтер durationcheck наверно можно доработать так, чтобы он ловил такое использование констант без явного указания «единицы времени», т.е вместо

            var duration time.Duration = 100
            time.Sleep(300)
            time.Sleep(time.Second + 42)
            

            надо было бы явно писать
            var duration = time.Nanosecond * 100
            time.Sleep(time.Nanosecond * 300)
            time.Sleep(time.Second + time.Nanosecond*42)
            


            1. Cerberuser
              02.06.2022 06:51

              В данном случае 42 — это не апельсины, а 42 наносекунды (те же яблоки).

              Ну то есть таки яблоки с апельсинами (секунды с наносекундами). Да, и то и другое - фрукты единицы времени, но толку, если оно читается не так, как работает?


              1. falconandy
                02.06.2022 08:25

                Нет, не яблоки с апельсинами, а только яблоки — «время». Не уверен, как оно для вас читается (секунда плюс 42 секунды?), но в данном конректном кейсе выглядит опечаткой как минимум — возможно вместо * ошибочно написан +.

                Аналогичный тип для «массы» выглядел бы примерно так:

                type Mass int64
                
                const (
                	Milligram Mass = 1
                	Gram           = 1000 * Milligram
                	Kilogram       = 1000 * Gram
                )
                
                var mass = Gram + 42
                


                1. Cerberuser
                  02.06.2022 08:32

                  time.Second + 42 читается как "секунда + 42 секунды", если не знать, что time.Second - это "миллиард наносекунд", а не "секунда". Точно так же и Gram + 42 читается на первый взгляд не как "1000 миллиграмм + 42 миллиграмма", а как "грамм + 42 грамма".


    1. AnthonyMikh
      01.06.2022 20:10
      +1

      Но time.Sleep всё ещё можно скормить голое число. Выглядит не очень надёжно.


      1. falconandy
        01.06.2022 20:24

        Написал выше про возможную доработку линтера durationcheck


        1. gecube
          02.06.2022 00:14

          там вообще чисел не должно быть... Я почему-то понимаю, как это написать на С++ на шаблонах... Но вот как это сделать на golang'е или пыхыпы, простите, не представляю...


  1. konst90
    31.05.2022 20:39

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

    Почему у программистов это не так? Языков много, и для всех помнить не получается? Или есть другие причины?


    1. F0iL
      31.05.2022 20:49
      +6

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


    1. Savevur
      31.05.2022 21:03
      +5

      Почему у программистов это не так?

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

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


  1. mjr27
    31.05.2022 21:56
    +1

    Thread.sleep(3000); //Ms

    не благодарите


    1. cepera_ang
      31.05.2022 22:06
      +4

      > // Ms

      Microsoft? Michael Schumacher? Megaseconds?


      1. mjr27
        31.05.2022 22:18
        +3

        Микаэль Шумахер, да


      1. mayorovp
        01.06.2022 22:20
        +4

        Microsoft и Michael Schumacher тут по контексту не подходят. Разумеется, этот комментарий может означать только "мегасекунды".


    1. 0xd34df00d
      31.05.2022 22:28
      +6

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


      1. mjr27
        01.06.2022 07:09
        +1

        Типа предложенное автором sleep_seconds = 3000 проверяется компилятором и.не может устареть, ага.

        Конечно, если есть возможность, обычно стоит передавать TimeSpan/timedelta/duration. Если нет, этот способ ничем не хуже любого другого.


  1. leremin
    31.05.2022 22:17

    Кстати, вспомнилось OpenXml. Там же дичь с единицами измерений шрифтов, полей, отступов. Где что сейчас не вспомню, но там и миллиметры, и дюймы, и 1/1440 дюйма, и проценты, и ещё невесть что.


  1. Max_JK
    31.05.2022 23:36
    -1

    еще можно так

    let sleepSeconds = sleep

    sleepSeconds(5)


    1. pehat
      01.06.2022 01:36
      +2

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


  1. Hlad
    01.06.2022 08:22

    Эмм, а обычные комментарии — это уже не модно?
    Вообще, за концепцию «хороший код должен читаться без комментариев, поэтому комментарии не нужны» надо бить. Оно, конечно, на каком-то самом низком уровне намного проще и приятнее разбираться в коде типа
    for NumberOfRecords = 0 to MaxRecordNumber...
    Немного проще, чем в
    for i=0 to a
    Но развёрнутый комментарий позволяет написать не только «что мы делаем», но и «зачем мы это делаем».


    1. dopusteam
      01.06.2022 10:13

      На самом деле, если дописать тело цикла, то будет намного понятен профит, когда будет обращение не к i, а к numberOfRecords


  1. Vindicar
    01.06.2022 11:01

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

    • загрузка/сохранение конфигураций. Пришлось городить костыль для хранения единиц измерения рядом со значением.

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

    • То и дело требуется явно проверить размерность, или явно привести к заданной размерности. Например, pint любит разбирать ньютоны (Н) на составные части (кг*м / с^2), что неудобно при выводе.

    • Пока нет поддержки типизации, т.е. нельзя сделать type hint с указанием размерности.

    Но в целом не скажу, что pint не стоил затраченных усилий.


  1. Panda_sama
    01.06.2022 11:34
    +2

    Глядя на запись "ждать 300 чего-то" - пусть даже там как-то будет указано "300 секунд" или "300 минут", у меня прежде всего возникнет вопрос - а почему именно 300?

    Мне кажется, в подобной ситуации напрашивается введение константы (в название которой можно указать sec300 или ms300) и использование в sleep(ms300) именно её


    1. baldr
      03.06.2022 13:13
      +1

      Отличное и правильное предложение.

      Однако не спасет от: sleep(something.get_duration())


  1. Color
    01.06.2022 14:27
    -1

    Это все последствия слабой системы типов.

    Пример, как это сделано правильно: в Golang есть тип `time.Duration`, в который можно передать данные в любом виде - миллисекунды, минуты, часы или что хотите. В итоге вопросов вида "что лежит в переменной" не возникает - там лежит длительность с абсолютной размерностью, которую при необходимости (в случае вывода в консоль, например) можно преобразовать к любой нужной величине - секундам, минутам и т.п.


  1. antirek
    01.06.2022 22:08

    что на счет js? и typescript?


    1. cepera_ang
      01.06.2022 22:09
      +4

      Избегайте.


      1. Keeper1
        01.06.2022 22:23

        Избегать JS или TS ?


        1. cepera_ang
          01.06.2022 22:25
          +3

          Да.


  1. nakem
    02.06.2022 09:17

    гошный линтер, который может помочь. https://github.com/charithe/durationcheck


  1. questor
    02.06.2022 22:32

    Поэтому у нас на кодревью принято заворачивать подобные конструкции и использовать спаны (C#):

    await Task.Delay(TimeSpan.FromSeconds(3));